Compare commits
1 commit
main
...
feat/syste
| Author | SHA1 | Date | |
|---|---|---|---|
|
53988d3ee2 |
40 changed files with 424 additions and 1509 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -21,5 +21,3 @@ AGENTS.md
|
||||||
k8s/charts/README.md
|
k8s/charts/README.md
|
||||||
.opencode/
|
.opencode/
|
||||||
opencode.json
|
opencode.json
|
||||||
*.pyc
|
|
||||||
.venv/
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ yaml-files:
|
||||||
|
|
||||||
ignore: |
|
ignore: |
|
||||||
k8s/charts/
|
k8s/charts/
|
||||||
**/sealedsecrets.yaml
|
|
||||||
|
|
||||||
|
|
||||||
extends: default
|
extends: default
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,3 @@ ethtool -K eno1 tx off rx off
|
||||||
```bash
|
```bash
|
||||||
helm upgrade argo-cd argo/argo-cd -n argo-cd -f k8s/helm/argo-cd/values.yaml
|
helm upgrade argo-cd argo/argo-cd -n argo-cd -f k8s/helm/argo-cd/values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
- create a new user and send them an email
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scripts/users.py add -u catalin -n cătălin -e catalin@roboces.dev
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,3 @@ spec:
|
||||||
service:
|
service:
|
||||||
dns:
|
dns:
|
||||||
type: LoadBalancer
|
type: LoadBalancer
|
||||||
web:
|
|
||||||
type: LoadBalancer
|
|
||||||
port: 8006
|
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: Application
|
|
||||||
metadata:
|
|
||||||
name: audiobookshelf
|
|
||||||
namespace: argo-cd
|
|
||||||
spec:
|
|
||||||
destination:
|
|
||||||
name: ''
|
|
||||||
namespace: apps-roboces
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
sources:
|
|
||||||
- chart: audiobookshelf
|
|
||||||
repoURL: https://charts.christianhuth.de
|
|
||||||
targetRevision: 2.4.0
|
|
||||||
helm:
|
|
||||||
valuesObject:
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
port: 8081
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: traefik
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
|
||||||
hosts:
|
|
||||||
- host: audiobooks.roboces.dev
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
- host: audiobooks.fuku
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
tls: []
|
|
||||||
audiobookshelf:
|
|
||||||
timezone: Europe/Madrid
|
|
||||||
config:
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClassName: truenas-nfs-csi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 5Gi
|
|
||||||
metadata:
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClassName: truenas-nfs-csi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 20Gi
|
|
||||||
backup:
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClassName: truenas-nfs-csi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 20Gi
|
|
||||||
library:
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClassName: truenas-nfs-csi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 500Gi
|
|
||||||
project: roboces
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
prune: true
|
|
||||||
selfHeal: true
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: Application
|
|
||||||
metadata:
|
|
||||||
name: authelia
|
|
||||||
namespace: argo-cd
|
|
||||||
spec:
|
|
||||||
destination:
|
|
||||||
name: ''
|
|
||||||
namespace: apps-roboces
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
sources:
|
|
||||||
- chart: authelia
|
|
||||||
repoURL: https://charts.authelia.com
|
|
||||||
targetRevision: 0.11.6
|
|
||||||
helm:
|
|
||||||
valuesObject:
|
|
||||||
enabled: true
|
|
||||||
pod:
|
|
||||||
autoscaling:
|
|
||||||
enabled: true
|
|
||||||
kind: Deployment
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
limits:
|
|
||||||
cpu: 1
|
|
||||||
memory: 1024Mi
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
port: 9091
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: traefik
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
|
||||||
secret:
|
|
||||||
existingSecret: secrets-authelia
|
|
||||||
mountPath: /secrets
|
|
||||||
additionalSecrets:
|
|
||||||
secrets-authelia:
|
|
||||||
path: secrets-authelia
|
|
||||||
items:
|
|
||||||
- key: authentication.ldap.password.txt
|
|
||||||
path: authentication.ldap.password.txt
|
|
||||||
- key: identity_validation.reset_password.jwt.hmac.key
|
|
||||||
path: identity_validation.reset_password.jwt.hmac.key
|
|
||||||
- key: session.encryption.key
|
|
||||||
path: session.encryption.key
|
|
||||||
- key: smtp_password.txt
|
|
||||||
path: smtp_password.txt
|
|
||||||
- key: storage.encryption.key
|
|
||||||
path: storage.encryption.key
|
|
||||||
- key: oidc.audiobookshelf.client_secret
|
|
||||||
path: oidc.audiobookshelf.client_secret
|
|
||||||
- key: identity_providers.oidc.jwks.key.pem
|
|
||||||
path: identity_providers.oidc.jwks.key.pem
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
existingClaim: authelia-data
|
|
||||||
configMap:
|
|
||||||
log:
|
|
||||||
level: info
|
|
||||||
session:
|
|
||||||
name: authelia_session
|
|
||||||
same_site: lax
|
|
||||||
expiration: 1 hour
|
|
||||||
inactivity: 5 minutes
|
|
||||||
remember_me: 1 month
|
|
||||||
cookies:
|
|
||||||
- domain: roboces.dev
|
|
||||||
subdomain: auth
|
|
||||||
- domain: auth.fuku
|
|
||||||
subdomain: ''
|
|
||||||
authentication_backend:
|
|
||||||
ldap:
|
|
||||||
enabled: true
|
|
||||||
implementation: lldap
|
|
||||||
address: ldap://lldap.apps-fuku.svc.cluster.local:3890
|
|
||||||
base_dn: dc=fuku,dc=local
|
|
||||||
additional_users_dn: ou=people
|
|
||||||
users_filter: (&({username_attribute}={input})(objectClass=person))
|
|
||||||
additional_groups_dn: ou=groups
|
|
||||||
groups_filter: (member={dn})
|
|
||||||
user: uid=admin,ou=people,dc=fuku,dc=local
|
|
||||||
attributes:
|
|
||||||
distinguished_name: dn
|
|
||||||
username: uid
|
|
||||||
display_name: cn
|
|
||||||
mail: mail
|
|
||||||
member_of: memberOf
|
|
||||||
group_name: cn
|
|
||||||
password:
|
|
||||||
disabled: false
|
|
||||||
secret_name: secrets-authelia
|
|
||||||
path: authentication.ldap.password.txt
|
|
||||||
storage:
|
|
||||||
local:
|
|
||||||
enabled: true
|
|
||||||
path: /config/db.sqlite3
|
|
||||||
notifier:
|
|
||||||
smtp:
|
|
||||||
enabled: true
|
|
||||||
address: submissions://mail.fukurokuju.dev:465
|
|
||||||
sender: Authelia <auth@fukurokuju.dev>
|
|
||||||
subject: "Your verification code is {code}"
|
|
||||||
identifier: auth.fuku
|
|
||||||
startup_check_address: test@authelia.com
|
|
||||||
disable_html_emails: false
|
|
||||||
disable_require_tls: false
|
|
||||||
disable_starttls: false
|
|
||||||
username: auth@fukurokuju.dev
|
|
||||||
password:
|
|
||||||
disabled: false
|
|
||||||
secret_name: secrets-authelia
|
|
||||||
path: smtp_password.txt
|
|
||||||
tls:
|
|
||||||
server_name: mail.fukurokuju.dev
|
|
||||||
skip_verify: false
|
|
||||||
minimum_version: TLS1.2
|
|
||||||
access_control:
|
|
||||||
default_policy: two_factor
|
|
||||||
identity_providers:
|
|
||||||
oidc:
|
|
||||||
enabled: true
|
|
||||||
hmac_secret:
|
|
||||||
path: identity_providers.oidc.hmac.key
|
|
||||||
jwks:
|
|
||||||
- key_id: authelia
|
|
||||||
algorithm: RS256
|
|
||||||
use: sig
|
|
||||||
key:
|
|
||||||
path: /secrets/secrets-authelia/identity_providers.oidc.jwks.key.pem
|
|
||||||
clients:
|
|
||||||
- client_id: audiobookshelf
|
|
||||||
client_name: Audiobookshelf
|
|
||||||
client_secret:
|
|
||||||
path: /secrets/secrets-authelia/oidc.audiobookshelf.client_secret
|
|
||||||
public: false
|
|
||||||
authorization_policy: two_factor
|
|
||||||
require_pkce: true
|
|
||||||
pkce_challenge_method: S256
|
|
||||||
redirect_uris:
|
|
||||||
- https://audiobooks.roboces.dev/audiobookshelf/auth/openid/callback
|
|
||||||
- https://audiobooks.roboces.dev/audiobookshelf/auth/openid/mobile-redirect
|
|
||||||
- https://audiobooks.fuku/audiobookshelf/auth/openid/callback
|
|
||||||
- https://audiobooks.fuku/audiobookshelf/auth/openid/mobile-redirect
|
|
||||||
- audiobookshelf://oauth
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
- groups
|
|
||||||
- email
|
|
||||||
response_types:
|
|
||||||
- code
|
|
||||||
grant_types:
|
|
||||||
- authorization_code
|
|
||||||
access_token_signed_response_alg: none
|
|
||||||
userinfo_signed_response_alg: none
|
|
||||||
token_endpoint_auth_method: client_secret_basic
|
|
||||||
- repoURL: https://git.roboces.dev/catalin/fukuops.git
|
|
||||||
path: k8s/services/authelia
|
|
||||||
targetRevision: main
|
|
||||||
project: roboces
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
prune: true
|
|
||||||
selfHeal: true
|
|
||||||
|
|
@ -12,7 +12,7 @@ spec:
|
||||||
sources:
|
sources:
|
||||||
- chart: authentik
|
- chart: authentik
|
||||||
repoURL: https://charts.goauthentik.io/
|
repoURL: https://charts.goauthentik.io/
|
||||||
targetRevision: 2026.8.*
|
targetRevision: 2026.5.*
|
||||||
helm:
|
helm:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
authentik:
|
authentik:
|
||||||
|
|
@ -64,8 +64,8 @@ spec:
|
||||||
minAvailable: 1
|
minAvailable: 1
|
||||||
service:
|
service:
|
||||||
type: LoadBalancer
|
type: LoadBalancer
|
||||||
servicePortHttp: 9001
|
servicePortHttp: 9000
|
||||||
servicePortHttps: 9444
|
servicePortHttps: 9443
|
||||||
annotations:
|
annotations:
|
||||||
traefik.ingress.kubernetes.io/service.serversscheme: https
|
traefik.ingress.kubernetes.io/service.serversscheme: https
|
||||||
traefik.ingress.kubernetes.io/service.serverstransport: apps-fuku-skipverify-authentik@kubernetescrd # yamllint disable rule:line-length
|
traefik.ingress.kubernetes.io/service.serverstransport: apps-fuku-skipverify-authentik@kubernetescrd # yamllint disable rule:line-length
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ spec:
|
||||||
sources:
|
sources:
|
||||||
- chart: forgejo
|
- chart: forgejo
|
||||||
repoURL: code.forgejo.org/forgejo-helm
|
repoURL: code.forgejo.org/forgejo-helm
|
||||||
targetRevision: 17.1.5
|
targetRevision: 17.1.4
|
||||||
helm:
|
helm:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: Application
|
|
||||||
metadata:
|
|
||||||
name: lldap
|
|
||||||
namespace: argo-cd
|
|
||||||
spec:
|
|
||||||
destination:
|
|
||||||
name: ''
|
|
||||||
namespace: apps-fuku
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
sources:
|
|
||||||
- repoURL: git.roboces.dev/catalin/fukuops
|
|
||||||
chart: lldap
|
|
||||||
targetRevision: 1.0.1
|
|
||||||
helm:
|
|
||||||
valuesObject:
|
|
||||||
lldap:
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
httpPort: 17170
|
|
||||||
ldapPort: 3890
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: traefik
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
|
||||||
hosts:
|
|
||||||
- host: ldap.fuku
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
- host: ldap.fukurokuju.dev
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
config:
|
|
||||||
ldapBaseDn: "dc=fuku,dc=local"
|
|
||||||
httpUrl: "https://ldap.fukurokuju.dev"
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClass: "truenas-nfs-csi"
|
|
||||||
accessMode: ReadWriteMany
|
|
||||||
size: 5Gi
|
|
||||||
secret:
|
|
||||||
existingSecretName: "lldap-secrets"
|
|
||||||
podDisruptionBudget:
|
|
||||||
enabled: true
|
|
||||||
minAvailable: 1
|
|
||||||
project: fuku
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
prune: true
|
|
||||||
selfHeal: true
|
|
||||||
|
|
@ -18,7 +18,7 @@ spec:
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
- chart: meilisearch
|
- chart: meilisearch
|
||||||
repoURL: https://meilisearch.github.io/meilisearch-kubernetes
|
repoURL: https://meilisearch.github.io/meilisearch-kubernetes
|
||||||
targetRevision: 0.38.*
|
targetRevision: 0.37.*
|
||||||
helm:
|
helm:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: Application
|
|
||||||
metadata:
|
|
||||||
name: pgadmin
|
|
||||||
namespace: argo-cd
|
|
||||||
spec:
|
|
||||||
destination:
|
|
||||||
name: ''
|
|
||||||
namespace: apps-fuku
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
project: fuku
|
|
||||||
sources:
|
|
||||||
- chart: pgadmin4
|
|
||||||
repoURL: https://helm.runix.net
|
|
||||||
targetRevision: 1.66.0
|
|
||||||
helm:
|
|
||||||
valuesObject:
|
|
||||||
image:
|
|
||||||
tag: "9.7"
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
port: 8010
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
ingressClassName: traefik
|
|
||||||
hosts:
|
|
||||||
- host: pgadmin.fuku
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
env:
|
|
||||||
email: admin@fukurokuju.dev
|
|
||||||
enhanced_cookie_protection: "False"
|
|
||||||
existingSecret: secrets-pgadmin
|
|
||||||
persistentVolume:
|
|
||||||
enabled: true
|
|
||||||
existingClaim: pgadmin-pgadmin4
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
size: 10Gi
|
|
||||||
storageClass: truenas-nfs-csi
|
|
||||||
test:
|
|
||||||
enabled: false
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
prune: true
|
|
||||||
selfHeal: true
|
|
||||||
|
|
@ -20,9 +20,6 @@ spec:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
service:
|
service:
|
||||||
type: LoadBalancer
|
type: LoadBalancer
|
||||||
httpPort: 9002
|
|
||||||
httpsPort: 9445
|
|
||||||
edgePort: 8005
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
|
|
@ -30,7 +27,6 @@ spec:
|
||||||
- host: containers.fukurokuju.dev
|
- host: containers.fukurokuju.dev
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
port: 9002
|
|
||||||
tls: []
|
tls: []
|
||||||
persistence:
|
persistence:
|
||||||
storageClass: truenas-nfs-csi
|
storageClass: truenas-nfs-csi
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ spec:
|
||||||
sources:
|
sources:
|
||||||
- chart: renovate
|
- chart: renovate
|
||||||
repoURL: https://docs.renovatebot.com/helm-charts
|
repoURL: https://docs.renovatebot.com/helm-charts
|
||||||
targetRevision: 46.254.*
|
targetRevision: 46.251.*
|
||||||
helm:
|
helm:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
renovate:
|
renovate:
|
||||||
|
|
|
||||||
18
k8s/argo-apps/system-upgrade-controller.yaml
Normal file
18
k8s/argo-apps/system-upgrade-controller.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-controller
|
||||||
|
namespace: argo-cd
|
||||||
|
spec:
|
||||||
|
destination:
|
||||||
|
name: ''
|
||||||
|
namespace: system-upgrade
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: https://git.roboces.dev/catalin/fukuops.git
|
||||||
|
path: k8s/services/system-upgrade-controller
|
||||||
|
targetRevision: feat/system-upgrade-controller
|
||||||
|
project: management
|
||||||
|
syncPolicy:
|
||||||
|
automated: {}
|
||||||
|
|
@ -26,7 +26,6 @@ spec:
|
||||||
enabled: true
|
enabled: true
|
||||||
service:
|
service:
|
||||||
type: LoadBalancer
|
type: LoadBalancer
|
||||||
port: 8007
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
className: traefik
|
className: traefik
|
||||||
|
|
@ -36,7 +35,7 @@ spec:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend: dashboard
|
backend: dashboard
|
||||||
port: 8007
|
port: 80
|
||||||
- path: /api
|
- path: /api
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend: api
|
backend: api
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ spec:
|
||||||
sources:
|
sources:
|
||||||
- repoURL: git.roboces.dev/catalin/fukuops
|
- repoURL: git.roboces.dev/catalin/fukuops
|
||||||
chart: vaultwarden
|
chart: vaultwarden
|
||||||
targetRevision: 1.0.3
|
targetRevision: 1.0.2
|
||||||
helm:
|
helm:
|
||||||
valuesObject:
|
valuesObject:
|
||||||
vaultwarden:
|
vaultwarden:
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Patterns to ignore when building packages.
|
|
||||||
# This supports shell glob matching.
|
|
||||||
*.tgz
|
|
||||||
.env
|
|
||||||
.git
|
|
||||||
.git_cache
|
|
||||||
.idea
|
|
||||||
*.md
|
|
||||||
docs/*
|
|
||||||
tmp/*
|
|
||||||
vendor/*
|
|
||||||
.travis.yml
|
|
||||||
.testignore
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
apiVersion: v2
|
|
||||||
name: lldap
|
|
||||||
description: Light LDAP implementation for authentication
|
|
||||||
type: application
|
|
||||||
version: 1.0.0
|
|
||||||
appVersion: stable
|
|
||||||
annotations:
|
|
||||||
artifacthub.io/images: "- name: lldap\n image: lldap/lldap:stable\n"
|
|
||||||
artifacthub.io/changes: "- kind: added\n description: Initial chart release\n"
|
|
||||||
artifacthub.io/maintainers: "- name: catalin\n email: catalin@roboces.dev\n"
|
|
||||||
keywords:
|
|
||||||
- lldap
|
|
||||||
- ldap
|
|
||||||
- authentication
|
|
||||||
- identity
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
# lldap
|
|
||||||
|
|
||||||
Lightweight LDAP authentication server with a web UI.
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install lldap oci://git.roboces.dev/catalin/fukuops/lldap --version 1.0.0 -n apps-roboces
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Kubernetes 1.19+
|
|
||||||
- Helm 3+
|
|
||||||
- A NFS storage class (default: `truenas-nfs-csi`) - only if persistence is enabled
|
|
||||||
- An existing `Secret` with credentials (see `lldap.secret.existingSecretName`)
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
All values are nested under the `lldap:` key. Example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
lldap:
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
httpPort: 17170
|
|
||||||
ldapPort: 3890
|
|
||||||
config:
|
|
||||||
ldapBaseDn: "dc=example,dc=com"
|
|
||||||
httpUrl: "https://lldap.example.com"
|
|
||||||
persistence:
|
|
||||||
enabled: true
|
|
||||||
storageClass: "truenas-nfs-csi"
|
|
||||||
accessMode: ReadWriteMany
|
|
||||||
size: 10Gi
|
|
||||||
secret:
|
|
||||||
existingSecretName: lldap-secrets
|
|
||||||
```
|
|
||||||
|
|
||||||
## Secret Requirements
|
|
||||||
|
|
||||||
The secret must contain the following keys:
|
|
||||||
|
|
||||||
- `LLDAP_JWT_SECRET` - JWT signing secret (generate with `openssl rand -base64 32`)
|
|
||||||
- `LLDAP_KEY_SEED` - Key seed for password hashing (generate with `openssl rand -base64 32`)
|
|
||||||
- `LLDAP_LDAP_USER_PASS` - Admin password for LDAP and web UI
|
|
||||||
|
|
||||||
Example secret:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: lldap-secrets
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
LLDAP_JWT_SECRET: <your-jwt-secret>
|
|
||||||
LLDAP_KEY_SEED: <your-key-seed>
|
|
||||||
LLDAP_LDAP_USER_PASS: <your-admin-password>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Values Reference
|
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
|
||||||
|-----|------|---------|-------------|
|
|
||||||
| `lldap.replicaCount` | int | `1` | Number of replicas |
|
|
||||||
| `lldap.image.repository` | string | `lldap/lldap` | Image repository |
|
|
||||||
| `lldap.image.pullPolicy` | string | `IfNotPresent` | Image pull policy |
|
|
||||||
| `lldap.image.tag` | string | `stable` | Image tag |
|
|
||||||
| `lldap.service.type` | string | `LoadBalancer` | Service type |
|
|
||||||
| `lldap.service.httpPort` | int | `17170` | Web UI port |
|
|
||||||
| `lldap.service.ldapPort` | int | `3890` | LDAP port |
|
|
||||||
| `lldap.config.ldapBaseDn` | string | `dc=example,dc=com` | LDAP base DN |
|
|
||||||
| `lldap.config.httpUrl` | string | `https://lldap.example.com` | Public URL for web UI |
|
|
||||||
| `lldap.config.databaseUrl` | string | `""` | Database URL (empty = SQLite) |
|
|
||||||
| `lldap.config.verbose` | bool | `false` | Enable verbose logging |
|
|
||||||
| `lldap.ldaps.enabled` | bool | `false` | Enable LDAPS |
|
|
||||||
| `lldap.ldaps.certFile` | string | `""` | TLS certificate file path |
|
|
||||||
| `lldap.ldaps.keyFile` | string | `""` | TLS key file path |
|
|
||||||
| `lldap.smtp.enabled` | bool | `false` | Enable SMTP for password reset |
|
|
||||||
| `lldap.smtp.enablePasswordReset` | bool | `false` | Enable password reset via email |
|
|
||||||
| `lldap.smtp.server` | string | `""` | SMTP server |
|
|
||||||
| `lldap.smtp.port` | int | `465` | SMTP port |
|
|
||||||
| `lldap.smtp.encryption` | string | `"TLS"` | SMTP encryption (NONE, TLS, STARTTLS) |
|
|
||||||
| `lldap.smtp.user` | string | `""` | SMTP username |
|
|
||||||
| `lldap.smtp.fromAddress` | string | `""` | From email address |
|
|
||||||
| `lldap.smtp.fromName` | string | `""` | From name |
|
|
||||||
| `lldap.smtp.replyTo` | string | `""` | Reply-to address |
|
|
||||||
| `lldap.persistence.enabled` | bool | `false` | Enable persistence |
|
|
||||||
| `lldap.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class |
|
|
||||||
| `lldap.persistence.accessMode` | string | `ReadWriteMany` | Access mode |
|
|
||||||
| `lldap.persistence.size` | string | `1Gi` | PVC size |
|
|
||||||
| `lldap.secret.existingSecretName` | string | `""` | Existing secret name |
|
|
||||||
| `lldap.debug.enabled` | bool | `false` | Enable debug mode |
|
|
||||||
| `lldap.resources` | object | `{}` | Container resources |
|
|
||||||
| `lldap.livenessProbe` | object | (see values.yaml) | Liveness probe |
|
|
||||||
| `lldap.readinessProbe` | object | (see values.yaml) | Readiness probe |
|
|
||||||
| `lldap.nodeSelector` | object | `{}` | Node selector |
|
|
||||||
| `lldap.tolerations` | array | `[]` | Tolerations |
|
|
||||||
| `lldap.affinity` | object | `{}` | Affinity rules |
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
By default, lldap uses SQLite stored in `/data`. To use an external database:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
lldap:
|
|
||||||
config:
|
|
||||||
databaseUrl: "mysql://user:password@mysql-server/my-database"
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported databases: SQLite (default), MySQL/MariaDB, PostgreSQL.
|
|
||||||
|
|
||||||
## Persistence
|
|
||||||
|
|
||||||
When persistence is enabled, a PVC named `lldap-data` is created. The PVC uses the `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Debug Mode
|
|
||||||
|
|
||||||
Enable debug mode to troubleshoot issues:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
lldap:
|
|
||||||
debug:
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pod not starting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=lldap
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl logs lldap-0 -n apps-roboces
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify configmap
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl get configmap lldap-config -n apps-roboces -o yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Default Credentials
|
|
||||||
|
|
||||||
If no secret is provided, the default admin password is `password`. **Change this immediately in production.**
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{{/* Expand the name of the chart. */}}
|
|
||||||
{{- define "name" -}}
|
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/* Create a default fully qualified app name. */}}
|
|
||||||
{{- define "fullname" -}}
|
|
||||||
{{- if .Values.fullnameOverride }}
|
|
||||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
|
||||||
{{- if contains $name .Release.Name }}
|
|
||||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/* Common labels */}}
|
|
||||||
{{- define "labels" -}}
|
|
||||||
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{ include "selectorLabels" . }}
|
|
||||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/* Selector labels */}}
|
|
||||||
{{- define "selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: {{ include "name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/* Chart name as the PVC name */}}
|
|
||||||
{{- define "pvcName" -}}
|
|
||||||
{{- .Values.lldap.persistence.name | default (printf "%s-data" (include "name" .)) }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{{- if .Values.lldap.config }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: {{ include "fullname" . }}-config
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
data:
|
|
||||||
LLDAP_LDAP_BASE_DN: {{ .Values.lldap.config.ldapBaseDn | quote }}
|
|
||||||
LLDAP_HTTP_URL: {{ .Values.lldap.config.httpUrl | quote }}
|
|
||||||
LLDAP_LDAP_OPTIONS__PORT: {{ .Values.lldap.config.ldapPort | quote }}
|
|
||||||
LLDAP_HTTP_OPTIONS__PORT: {{ .Values.lldap.config.httpPort | quote }}
|
|
||||||
LLDAP_VERBOSE: {{ .Values.lldap.config.verbose | quote }}
|
|
||||||
{{- if .Values.lldap.config.databaseUrl }}
|
|
||||||
LLDAP_DATABASE_URL: {{ .Values.lldap.config.databaseUrl | quote }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.ldaps.enabled }}
|
|
||||||
LLDAP_LDAPS_OPTIONS__ENABLED: "true"
|
|
||||||
LLDAP_LDAPS_OPTIONS__CERT_FILE: {{ .Values.lldap.ldaps.certFile | quote }}
|
|
||||||
LLDAP_LDAPS_OPTIONS__KEY_FILE: {{ .Values.lldap.ldaps.keyFile | quote }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.smtp.enabled }}
|
|
||||||
LLDAP_SMTP_OPTIONS__ENABLE_PASSWORD_RESET: {{ .Values.lldap.smtp.enablePasswordReset | quote }}
|
|
||||||
LLDAP_SMTP_OPTIONS__SERVER: {{ .Values.lldap.smtp.server | quote }}
|
|
||||||
LLDAP_SMTP_OPTIONS__PORT: {{ .Values.lldap.smtp.port | quote }}
|
|
||||||
LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION: {{ .Values.lldap.smtp.encryption | quote }}
|
|
||||||
LLDAP_SMTP_OPTIONS__USER: {{ .Values.lldap.smtp.user | quote }}
|
|
||||||
LLDAP_SMTP_OPTIONS__FROM: {{ printf "%s <%s>" .Values.lldap.smtp.fromName .Values.lldap.smtp.fromAddress | quote }}
|
|
||||||
{{- if .Values.lldap.smtp.replyTo }}
|
|
||||||
LLDAP_SMTP_OPTIONS__TO: {{ .Values.lldap.smtp.replyTo | quote }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.lldap.replicaCount }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "selectorLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
{{- include "selectorLabels" . | nindent 8 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.lldap.podSecurityContext }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.debug.enabled }}
|
|
||||||
initContainers:
|
|
||||||
- name: debug
|
|
||||||
image: alpine:3.19
|
|
||||||
command: ["sleep", "infinity"]
|
|
||||||
envFrom:
|
|
||||||
{{- if .Values.lldap.config }}
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ include "fullname" . }}-config
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.secret.existingSecretName }}
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Values.lldap.secret.existingSecretName }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.persistence.enabled }}
|
|
||||||
volumeMounts:
|
|
||||||
- name: {{ include "pvcName" . }}
|
|
||||||
mountPath: /data
|
|
||||||
{{- end }}
|
|
||||||
securityContext:
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
readOnlyRootFilesystem: false
|
|
||||||
capabilities:
|
|
||||||
drop:
|
|
||||||
- all
|
|
||||||
{{- end }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.lldap.image.repository }}:{{ .Values.lldap.image.tag | default .Chart.AppVersion }}"
|
|
||||||
imagePullPolicy: {{ .Values.lldap.image.pullPolicy }}
|
|
||||||
{{- with .Values.lldap.securityContext }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml . | nindent 12 }}
|
|
||||||
{{- end }}
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: {{ .Values.lldap.service.httpPort }}
|
|
||||||
protocol: TCP
|
|
||||||
- name: ldap
|
|
||||||
containerPort: {{ .Values.lldap.service.ldapPort }}
|
|
||||||
protocol: TCP
|
|
||||||
envFrom:
|
|
||||||
{{- if .Values.lldap.config }}
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ include "fullname" . }}-config
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.secret.existingSecretName }}
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Values.lldap.secret.existingSecretName }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.persistence.enabled }}
|
|
||||||
volumeMounts:
|
|
||||||
- name: {{ include "pvcName" . }}
|
|
||||||
mountPath: /data
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.lldap.resources }}
|
|
||||||
resources:
|
|
||||||
{{- toYaml . | nindent 12 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if not .Values.lldap.debug.enabled }}
|
|
||||||
{{- with .Values.lldap.livenessProbe }}
|
|
||||||
livenessProbe:
|
|
||||||
{{- toYaml . | nindent 12 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.lldap.readinessProbe }}
|
|
||||||
readinessProbe:
|
|
||||||
{{- toYaml . | nindent 12 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.persistence.enabled }}
|
|
||||||
volumes:
|
|
||||||
- name: {{ include "pvcName" . }}
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: {{ include "pvcName" . }}
|
|
||||||
{{- else }}
|
|
||||||
volumes:
|
|
||||||
- name: {{ include "pvcName" . }}
|
|
||||||
emptyDir: {}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.lldap.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.lldap.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.lldap.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{{- if .Values.lldap.ingress.enabled }}
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: {{ include "fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.lldap.ingress.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
{{- if .Values.lldap.ingress.className }}
|
|
||||||
ingressClassName: {{ .Values.lldap.ingress.className }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.ingress.tls }}
|
|
||||||
tls:
|
|
||||||
{{- range .Values.lldap.ingress.tls }}
|
|
||||||
- hosts:
|
|
||||||
{{- range .hosts }}
|
|
||||||
- {{ . | quote }}
|
|
||||||
{{- end }}
|
|
||||||
secretName: {{ .secretName }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
rules:
|
|
||||||
{{- range .Values.lldap.ingress.hosts }}
|
|
||||||
- host: {{ .host | quote }}
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
{{- range .paths }}
|
|
||||||
- path: {{ .path }}
|
|
||||||
pathType: {{ .pathType | default "Prefix" }}
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: {{ include "fullname" $ }}
|
|
||||||
port:
|
|
||||||
number: {{ $.Values.lldap.service.httpPort }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{{- if .Values.lldap.podDisruptionBudget.enabled }}
|
|
||||||
apiVersion: policy/v1
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
metadata:
|
|
||||||
name: {{ include "fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
{{- if .Values.lldap.podDisruptionBudget.minAvailable }}
|
|
||||||
minAvailable: {{ .Values.lldap.podDisruptionBudget.minAvailable }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.lldap.podDisruptionBudget.maxUnavailable }}
|
|
||||||
maxUnavailable: {{ .Values.lldap.podDisruptionBudget.maxUnavailable }}
|
|
||||||
{{- end }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "selectorLabels" . | nindent 6 }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{{- if .Values.lldap.persistence.enabled }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: {{ include "pvcName" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- {{ .Values.lldap.persistence.accessMode | default "ReadWriteMany" }}
|
|
||||||
{{- if .Values.lldap.persistence.storageClass }}
|
|
||||||
storageClassName: {{ .Values.lldap.persistence.storageClass }}
|
|
||||||
{{- end }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: {{ .Values.lldap.persistence.size | default "1Gi" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.lldap.service.type }}
|
|
||||||
ports:
|
|
||||||
- port: {{ .Values.lldap.service.httpPort }}
|
|
||||||
targetPort: http
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
- port: {{ .Values.lldap.service.ldapPort }}
|
|
||||||
targetPort: ldap
|
|
||||||
protocol: TCP
|
|
||||||
name: ldap
|
|
||||||
selector:
|
|
||||||
{{- include "selectorLabels" . | nindent 4 }}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
lldap:
|
|
||||||
replicaCount: 1
|
|
||||||
|
|
||||||
image:
|
|
||||||
repository: lldap/lldap
|
|
||||||
pullPolicy: IfNotPresent
|
|
||||||
tag: "stable"
|
|
||||||
|
|
||||||
service:
|
|
||||||
type: LoadBalancer
|
|
||||||
httpPort: 17170
|
|
||||||
ldapPort: 3890
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: false
|
|
||||||
className: "traefik"
|
|
||||||
annotations: {}
|
|
||||||
hosts:
|
|
||||||
- host: lldap.example.com
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
tls: []
|
|
||||||
|
|
||||||
config:
|
|
||||||
ldapBaseDn: "dc=example,dc=com"
|
|
||||||
httpUrl: "https://lldap.example.com"
|
|
||||||
databaseUrl: ""
|
|
||||||
verbose: false
|
|
||||||
ldapPort: 3890
|
|
||||||
httpPort: 17170
|
|
||||||
|
|
||||||
ldaps:
|
|
||||||
enabled: false
|
|
||||||
certFile: ""
|
|
||||||
keyFile: ""
|
|
||||||
|
|
||||||
smtp:
|
|
||||||
enabled: false
|
|
||||||
enablePasswordReset: false
|
|
||||||
server: ""
|
|
||||||
port: 465
|
|
||||||
encryption: "TLS"
|
|
||||||
user: ""
|
|
||||||
fromAddress: ""
|
|
||||||
fromName: ""
|
|
||||||
replyTo: ""
|
|
||||||
|
|
||||||
persistence:
|
|
||||||
enabled: false
|
|
||||||
storageClass: "truenas-nfs-csi"
|
|
||||||
accessMode: ReadWriteMany
|
|
||||||
size: 1Gi
|
|
||||||
|
|
||||||
resources: {}
|
|
||||||
|
|
||||||
livenessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
timeoutSeconds: 15
|
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
periodSeconds: 10
|
|
||||||
|
|
||||||
readinessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 15
|
|
||||||
timeoutSeconds: 2
|
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
periodSeconds: 10
|
|
||||||
|
|
||||||
nodeSelector: {}
|
|
||||||
tolerations: []
|
|
||||||
affinity: {}
|
|
||||||
|
|
||||||
secret:
|
|
||||||
existingSecretName: ""
|
|
||||||
|
|
||||||
podSecurityContext: {}
|
|
||||||
securityContext: {}
|
|
||||||
|
|
||||||
podDisruptionBudget:
|
|
||||||
enabled: false
|
|
||||||
minAvailable: 1
|
|
||||||
|
|
||||||
debug:
|
|
||||||
enabled: false
|
|
||||||
|
|
@ -2,7 +2,7 @@ apiVersion: v2
|
||||||
name: vaultwarden
|
name: vaultwarden
|
||||||
description: A Helm chart for Vaultwarden password manager
|
description: A Helm chart for Vaultwarden password manager
|
||||||
type: application
|
type: application
|
||||||
version: 1.0.3
|
version: 1.0.2
|
||||||
appVersion: 1.37.1
|
appVersion: 1.37.1
|
||||||
annotations:
|
annotations:
|
||||||
artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.1-alpine\n"
|
artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.1-alpine\n"
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ vaultwarden:
|
||||||
securityContext: {}
|
securityContext: {}
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: LoadBalancer
|
type: ClusterIP
|
||||||
port: 8008
|
port: 80
|
||||||
targetPort: 80
|
targetPort: 80
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ server:
|
||||||
redis:
|
redis:
|
||||||
image:
|
image:
|
||||||
repository: docker.io/library/redis
|
repository: docker.io/library/redis
|
||||||
tag: 8.10.1-alpine
|
tag: 8.10.0-alpine
|
||||||
|
|
||||||
repoServer:
|
repoServer:
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,4 @@ spec:
|
||||||
- ghcr.io/antoniolago/charts
|
- ghcr.io/antoniolago/charts
|
||||||
- https://repo.helmforge.dev
|
- https://repo.helmforge.dev
|
||||||
- https://harish2k01.github.io/helm-charts/
|
- https://harish2k01.github.io/helm-charts/
|
||||||
- https://helm.runix.net
|
|
||||||
- git.roboces.dev/catalin/fukuops
|
- git.roboces.dev/catalin/fukuops
|
||||||
- https://kubereboot.github.io/charts
|
|
||||||
- https://charts.goauthentik.io/
|
|
||||||
- https://git.roboces.dev/catalin/fukuops
|
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,3 @@ spec:
|
||||||
- https://git.roboces.dev/catalin/fukuops.git
|
- https://git.roboces.dev/catalin/fukuops.git
|
||||||
- https://democratic-csi.github.io/charts/
|
- https://democratic-csi.github.io/charts/
|
||||||
- https://bitnami-labs.github.io/sealed-secrets
|
- https://bitnami-labs.github.io/sealed-secrets
|
||||||
- https://kubernetes-sigs.github.io/descheduler
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,4 @@ spec:
|
||||||
sourceRepos:
|
sourceRepos:
|
||||||
- code.forgejo.org/forgejo-helm
|
- code.forgejo.org/forgejo-helm
|
||||||
- git.roboces.dev/catalin/fukuops
|
- git.roboces.dev/catalin/fukuops
|
||||||
- https://git.roboces.dev/catalin/fukuops.git
|
|
||||||
- https://git.roboces.dev/catalin/huesoporro.git
|
- https://git.roboces.dev/catalin/huesoporro.git
|
||||||
- https://charts.christianhuth.de
|
|
||||||
- https://charts.authelia.com
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: authelia-data
|
|
||||||
namespace: apps-roboces
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
storageClassName: truenas-nfs-csi
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
kind: SealedSecret
|
|
||||||
apiVersion: bitnami.com/v1alpha1
|
|
||||||
metadata:
|
|
||||||
name: secrets-authelia
|
|
||||||
namespace: apps-roboces
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
name: secrets-authelia
|
|
||||||
namespace: apps-roboces
|
|
||||||
type: Opaque
|
|
||||||
encryptedData:
|
|
||||||
authentication.ldap.password.txt: AgBwsqdgEOQ8r/yUOD82Cwr44jxqV9RPDTzMYju8IfWx7b4iDKtZVidLK4RxBCp1+MPyWmLs7H4jec3Xsr10da/V+LaS1Elib+Sp2CkFZPGj1/XAcu5w3k/BnMcsRWJEJtzU251NVon6IVEzszcGOaVsPqf6aIvnKgOJEmp1boMlnemkvtaeXVS0TuSLXCQEMDfGMh9X8D9je315Sdqpczo8YLz6jyj6RxlgXeygRXkXANd2KFOyfLNUAnzm4Tl6PMjULnXvsxLhlc35+am6IsZGVLcdRMPY12UpHeZyVIgJBZSdo/gLTJS/hLhL2sPJv8HksL6YfvDnIWqu3PxVwI3BGtW2znBQaEIFXtOIXL9EDG0NMAZSBYCRpjrPNHP7xj7ruqjg4eU1iUj6yJpIv9322L/qhFWViK9wXc3+0L2n2q4qVBgrZxcacgqtHrCYj5Uo9iluVPWwagcKFYRXBupqaUUTso7LtSsv4WbOrJzYzE9mZk+feH1ZNvBp13JfWNdRiso5rpl0tZFJ7oW4G/CJuWg2MmL3UNViYB/ntwFWZWT8dxCEq2LI3UxeawHntzvk82kFyZduLUwhLvY4WfQB4yb//gBOpc0OyHa+djwECIyy3Jh+70LG+1YeVc8yQm52eu9+mzRSwDlqspKZYfwbMPCCiQj6AiSfg9Ho5+ITq1PgEt3oo9b0JkYFmEAgT88Y6z0DRQrM9YOXCfazMg==
|
|
||||||
identity_providers.oidc.hmac.key: AgCMb1NgRFW7jB8xAXDzTDK336k1w63FNJGJPj9fUrkM/5QT6WuAAPYPeD/3PWG/+0XjUycU4Q2PK/ei8rK6BJ1jwdSRQqBe/ybdxl3WCWGmjB2/jKLIAFQonJdgIzZiY4rUHUepQqhva/wA0eM1nCV/KYuOCIIzdNAGj3E+zyP9yquZcDxknadLuvY6XbTTCdsdwYgFP9qYy8+Vg/K8c2GGubuU34uLkfCG3afow9/gvtSDU5lsZwGtZPcYibOVULQ/CEiGiXBXs7g5W1HjqcJdPaJZDfkG2hPWh4aWMxMkjORt/9NrNmBi70e4LW5cq32wX0Mz+UblDf66Qz3wnGmZ93owAw1N0MtlI/V0W2XvSnxcXvmloHGfbll2rhFiUFPFmWq71Uv52Qc8rAMOeuJgRnrlG3QHxLFkTonMYcasQrfm8MjHpaNkemsdA6XO9VvrRVxLyN31lV+2bbeqh2wHZgCRIbU4gUyApi69Rd1TyEYVW9QSxWbPkX5B5eaCqDCZLX9aQocfElrKk/zzvY2J53s07AkvsowgwbWhwpzwAYB2ajBndAcNhzS311ve1kzZlp498ePGCdpcyuTJfVWTKoXdmJu3xSg0P7Czts/zstQYGSpp+0I4gwzYTtSuf1M841Obc78l2PaUqN5P3YIHdq4ZiMTWPHhqgerobyf6XxtXcO5iEf7zSGYexRwaTroMp/Sqx9P0eTX1rufwkmvMlPH6mClr3RMVGno7uFuC1SkRM3Ox0a4wJ0Y2B5IX/7C+Pd68Z1VQX+wJeson0X2K
|
|
||||||
identity_providers.oidc.jwks.key.pem: AgBNZRQSUb3ibWMO/1ZWtXnNoj4e6/kp1PqQW7PA4Kw9VDnTKa4s3iG6BNl1cheUAZKfjNTGBfOlj4CNrUaa4T750wh8dHp2l5BXpAj0Vf1lV8ttPp79Q+AJkLVMhybtlcnczmdDGX8K99g1uC1Tv9aKTrSXQTQV4GJAjnwmj9pUvhxNI2PJiFWfXtoaXG3co5FJInAwrM5jBGplLb4u+TRWbmFb6esV9RFWZLRZcwFXKPr0ewg4rlH0Yqt9nxGOarUqGsNKTRusHw4dhcY7Kp4jNKR6Oa2s7gvDl1hsPkjM1JiZbWKMdboJxDUCA1AJtY/TXsTsklYzYJq6tnR+m8vzhema5l3pR4Vwt1Pw9NV7xear9AOY5nc2g+zB4StbSt1ezfjXCyE/z6geZ9QXTwwGywxnHW7VXA79lbwEWGoaiFwSVdd/tN5NJcbTINr0yYlLLUaRHorG1Q3DbnIr372dvo+bxPyCJW70lkYyGS/HXfPjsZ0IgZJa0/UxFYUuFjF5Yiaw9D6WM6kUSBfjheo152qvUkeVswYBtM0EKxY1NaZn2aVtMUpkupeU0yd9oBV27r4wUXBDxNP+mrGrKXkSfJAOuOGPQRoUAAfhhnBWM9/B3ngZMeKZWBZ/oCM18k2YWb8Ap/4/LoCXL6u2gjTQQgrE75OqNN0MOk33XM7KmLZQNmB0BbWywgU8vry6PXgPWPhzJHPuEGokLSCTFydEq19n+tqcrxOWTMmjYGxECf6mtxDeR+6sgb67MRNUeGSl3nbyOR6PGCKb6P9WR6LoMz2+AAUpw8uDJm2RDHSpAjl8PSz6zzbrP2dUytqeYULc8gxq8urNocPwAfDJqM/vTMekf4b9VhMEWhoXUzMkcghnc57sefdBZFDT7B95Bx4dNGPyuzKdQH650UfLyepQ6nGJefQCe/t2Fwb9H/HsCGm7+s+ODNjA6TixDpbxP10zemn/+FGHUBlG6iaIPQylEOP/GBltXazZZHs5J7RuFpckWRO97jlAbbbhlEUK5OIqigZIlS3QTxDnI7tBh8XWEw0kDN2tuigZ1/+HTuHtvpZJQTug6AYeP7kX+a6GVwz9RKO4QNb7atGdtsCpK9WQ6yhz8YlA5z4y+OyIobUCyh+fGMVZiYTRoO6PaXZBzyf0BvQlTDBClhan+QGo0+oEKUW4lEMZDlas9DIKJFmv9ugd1+9/tdbkPhKI1P6dx1wqNV7/Ovx7f8HE0JJxBxvwZmzeglCidtw8nlxaebAZIAzMLARE/bVCclqcu22ymN6i2wfN3OD8UXY3ehb9oJxab5v+dTR6acEQtMdnZJ6Mex1Wegxy527Yd9ZhbWEGSAh/fkFiHJcp05sA2EIS1s6D+yOXOlGWq6JSuD2XoCPxjKwe6WVudqR4VwvbKmhQT6L8HZ4Qfo3b7PVHNMA1ae8UFE8pj1NhL85t5nb4Q5gQUZ8khDqO3FthGhpsrZLh0+FAbN5+9BPEHe51W2NQpB9hmY3ZRr8csOD4CKkVjs4OHYcQJrN029L7mlzXQ1SI2DkwJY+0ok66h+o2ZKdEPbKj1fono0Cg9eeBCgJEj7D3EvNoRpT0PpoL5y0vHojHg7tHjz9mmz5d0VvehMtvQl3JOxo/++8xc1YJUy/S1g9QEx1WPLl7zJGtWeDd8BXkzFzonPMU0mMbeG+Gl7Wfb6EdLzEE67c/mbIVGxqz6lHsXY7mh0iugGuyJ84c5+yX5r7fjTDT+OR6PQxHu95DKO6Hkqq9A2wNTl0OhqTjtBo1LzrFOlFwufd08WhguzUOu4eZXGc2Nz0iPBc3u2VlqPORqH1WwFZyAGCdnX1f9rkLoVF8eXbSkd+W3soy5Nd27Yp9upgilyZWIUo1N4NVQhNxMTvtHHH5w4hORch6sTCeBGe/SlzlBnD/8KW1ETfy/6H1YrRU6jhyXuj5tM0v4GLOZyCzcG5Nf+POdCdgp4mzg5X6anE3fR/jfbFonsUv5KGqLbIPDow8lNW3j1ACiePtOl/Vz3P9OnmqGHqdoTOfi2TUeN6OgtL+pXwXxBCJZXDsTrHamoHw/4RMWnjz7ABdFrh8aOA/rOSaO4Vf+BJaWqekx3KaJNLrhGNtKNACvQ+klk0HceRRKgKOcbcTTH7qxpFmR/qYkmyFkG2xBkpj4KfSmW64BAK5eNlWPCmLL6Vkql1xIGznnV1ahowlPuXnwxI2PSPBhKFOPeI3ngr3pb1kxjg6pjoA6R2ON8XVJQ7uu5ORXaihehIibZd3VgoxAd00BxWCdsqgYb9kYoRFXn62LZ/6MzIAgI2gOhy7n1UuUtJQUB+O7quEiHGtRPRER2zDWQ9lhREVVqBGfnjU7wbHEOOzImcvJv0ei8yV8vkuChky2dSmitrCW0XMgnlcbLuBrn2id0JQjBjB4HKhPZM0UtipaayEworkdS3jH+Z7z8+qPUFH04k2hK54JWkZAUB1X75BPsbxzx5EwThGZ5B2xVK37MAiguT1YWSeMPe01q9jO+/8F5VaLuw8Gn8/Elm6aNordKUKUUdE6xIDT+CV/lNWmPBGKXVDMB1CLRwtzBbvwSkErGUL1XKdqPQ4nr2L89EElHyrwEVMd7FaqiKLEfB7RtZreCuYIKcXyy18LUWq+3gasjWVrYsoWscYrslJxJMHvhcSfX6tCB5du2XZ+e7KyAEvvIagMulBRptd52/prxEmLqJktAvImw6T+ffaFZMd8aPrHsr3JJNfL/WJLY88ECoBakzT7F5/uL6u3c9WyhLnbTgwdO6QsT5/v9OlXx4JJK6w/qkXJ8176YiedxMSciPCMbXSbDHB6hqj3di+0dHL09p9x5/Jg6PLEy5vaGZXoJiZtSTnF9xpCwF+nroPiO9iV/JGrQ6W/DdlbWZK3+5pXjRsALkYA7dOCS6RPcboFSdDxSDFcUrk8wOGekVTKJG8/PZS6vgqmSUe97/zPuDkwUX1qS1SJCqdkkNiGbLVGqw=
|
|
||||||
identity_validation.reset_password.jwt.hmac.key: AgAQRknTOoC5tbCitLZkRtcIs3VOaubEvlhRmGtHh/OS0v6lgU2Gcl5C6jisu/+MqQba4euzMACLVgSMiEkERY2GrMijecfVf5LOkKKwQ0Xjiw4a+MlSVs5KR1Ovlz414//3GfUCttunnDRnH6xQbsQjWLCI6lv6Umb40TQHOnRmb83wmSkBa/P9WR35EQ6IcES3q7Ad4ZpNtLaavEnB8NuLZg3yaJpN+1yuZPeMgTPUtTG/e9FLdBs3S8AYFIcrUnDLViUo9DfXumMs1DlTsmABpjyIgjIeywOmCxMYZYvQCJrRGYlKZRdoCoafhAPxt67hdUYOe56cq3kqlTNTdURMbYup7cIkW2hQ8ZiyPvYk++RIhV/cPWOSjh6CLxMq26Si1c2Lg3aAVFxQcDiXbdprjHzbKzFfKO/5LVnhT7O651BqkCguPC2vVoaeuxblHjqouBD7ZmPazIQ9J/uDUmvm5UPjEN+Ln+enrLXdoJN9HMor410/PJ+9xPAHXhdrXRS6j5TlN5q1pz0gj2Qmnv3cunsyLo+ZJ97F3EkSYPVoK3sMXGn2fdKy/lc9vPMMvtH/tLdhWefxd25APahStZHv4Mb7O6Z+A4uFiP9Qao64jeLj08LWBbJPefT7Y+KthugN6cW4y/xQN4UFvpPHhGB36yb+45eIBzOJHltOqZ+wldjUHeBgMdkODAQYYbCzhDuVVm2iMGQqUi/Vk+EayaV70prcAELgRskseME2MwfrhYMAKS91tfMbL2dGTQ==
|
|
||||||
oidc.audiobookshelf.client_secret: AgB5usnmgdDGm4MjtMTZDxhGb6qCxoMqx/2f3tZ7JYDgoVEspeL2tFBxXBOUDamCxpnuSp12O1ZcUpOw+9XAj4079vG7sWHAzjX0nIIJCNIjH3Y6+rhGh7J7nnWc9UJqzeWM6T4k9Dntyhsn96Xp5RDSUqhH68/u4OeqwzRJJofHlbqLmYq6+bq4MZfv/wZIsqfA02aWFJk7ST2qqD1Sc+oz0HkP8jXk9WwCCnRCuZoZgYUTHbPZAgOLMMRTlqTF4jHjFQ5B+IQTh2z53Agq6G8QLmU49J7RtPIunG+2Gf71KYXIeKktH8QwI9nBof1PXGYMmOvCCzqoBLsIBqYg/iAQ+kjlkbJov/3SBk+uwdj/wvG99+AJPVB2VHr8rinW0WXptzUATqYrHg0ufBxX4lKvVhqdkY7AaHPoI5ZwKi7AYftuo0IxDHGYC4GXVg9mWY7T6nNcvqB6s1epBNDjQnsCk+krxfZ83OecHUA4apwrSX4/Ytc+GkuQ/eJcdr9SoMXW7yzFfOLMh4+8syH7xigLwszw0t6lxJ37DnVbuYewhvazIU2TBWyTtQYnBeqVm2Wllh6xZiP5PyJ+VEi2fwGZVW6NX0Hh0gVmYNduj8xAJS4GRLwE2cOrN99kTMIXBdqmjXdYuAQKYDMlWU8QtTqtsoa4Y3UV/s7f7DdVtW+nIwxHFStNSQE4kOqPtUpgKbJPpPTjlPmIhW5Zm1gj3eOWIRLRV6UAkiJ2t6T2BEh+uqhcf26pbSjbfnqSEPVhfAzarnFV32Dkgue0I6rka/GPlf4fSK89JuXT9Igwdm0cKwOepYynZIf0NSSB1bWcgTTuL3N4CjteWc/XzacFkXvWuNJ3/WgN/brGZb2nRmmHEA==
|
|
||||||
session.encryption.key: AgCjw3K7solE1INH+VIwEeP2U70HCaY25Iu6sq5C7j8nuW8JzbSCBPeY+qTS76l4sIyLlLekk7ke5kCPpZo+Jsz62UQJMGaSASRy0FKQIxgn5UIkObB+qziGjC8WNj6y/JmbvPNiIp+aw0Nd3OmVLRkvIjJDkyMf/iW1oGeLctwRop9Y+wi7lDPyEHH+CaNPCLYidhAJFG785qibqY8jTxtHsEts2T+hSXz75v5abYS+lTFtFV7RDWudkWG6dPjUKwYGw7l/yQm5p2qzzvhoyCrQItdNuEvGrZQsnXZlq4ik0C7t977uiU+fumYKFQvHzVuQTMjjo9G5AlhUG/4w2olMqOMZoJ0u+hHFBX1K1uYCUr1hd2343jd+y86iObZzTazlTGdP142lXk9+CT+akpQIVX0Mk5mwtemCYG+64ebSQm+SVB9Ffgxx3Ips6BOhqwIO0fLB1iz4BFVUp+87juyHZOP/5uC+KeV3+DHaQDrHRo6DxF5D5CRXBCcHXeWBLkDXSENgD4kvRee7fEQf60ajvi9VFTPjk86KXCbOZVlZPPrqWG7Dh+kaYOjWCQXQvFzYAq6/Dr5ZDRPJ/X989zHjPFFGRQ7Ol9uyjT/7I/2IBfdQB1Ep5BWty50PBVIqbYKRzQN0g0x5BAj5/2J9mOX/0BNGExVCNn53rfc8yDurfP0q1nbYk1t+fv0T9oI7RTPYIVWtUnshG8fwYK1PARNYBbKrGwjWtTdVxzV+ocunmmOHQhvOFrTnsfupGQ==
|
|
||||||
smtp_password.txt: AgB8pRbQxScSBRty04Z5oen7F52Y7PnwF9gJ6fdbUTRobE7j2fWiHMQk4l1FTztW3Muz3B/MQZBgBEzulpkDJ1TrvTe5I7JFdANr3lmlKjy0CG2NcQ7Vma7Bqu2SVnL8wxi/3IBTGxVa6bplGrkUCsIr/qWLhkVknOpAaUvOUuECT8PVxUZq/s4UMddkEf4N3IQW7j/60xwzomMp4e5eZ8YTH0/ZGcuw9JOQSOoJDzvJ5B+wZF7q7j9LQc4gBPHQiFFMVlZrhHA0fdjfPxbb7QEiGdMBxeZ0JqaaFBnr4FG7hRY5/H9RS9iZCFH9QY6X86ul9ZgiJYVyrvZFKSBsW+THlLZ7MACLvOs/zdXhpc2SthccoK8ykGC/inWTnfodQpH7N4zJq7akXxFy9/em4ssjWul4coeLNc+xhyi5VauLQQfRpgijc1kVJeQbrUrMe+aE1tlRdgRx76OKiFym1t47+zN3eqRf+NegNBGPSdAtKEBKfNPrcFQmlZVR6fRZqiBxG0wE90nfq9fuw3RDnVU/0LsuxwTz99gWMWl8ySuo+EAhf+JmB26/Pi1hnByeNeD8FsVkUiC6XnwAVC+ytnaVEfatrKmSJyzRhTSuFUoFxD5YV0aQgILks4+CpQwn300s0bqkLOP0etigZCCPq4cR+t+FFYAkuVjr+xPsoww0UOgokHsZKoXYi0n96UjbT0w+zoHhqQTBrPPWhPjDogZBKBCnth5eUtgrhSA2
|
|
||||||
storage.encryption.key: AgBt8lTRtRqn+PiTNjgmJ97/rEqKEE70KZLuVtyvBM2ek70E555vx16BsCN3ATvwcr5dw+nAwYKpHJzS+mgTloLWmz6L2HqtxhlnaKC7elFeOza7tPKmKeKV3zXQ8YEA9eWjKdUN2TzO5s10iPCp4esUy5asO5DS/QVyPHMUfufu3iwHCDgnDEGlQ0pO1LMT2FwJ2QYX4Cub+OjUVrkBm8VbNdFqIw3OBtahRX2gx+o1d55psfi08ylEMKUwkHXYtkBheDvYL7SRv8xHKHGrN1loWHZOby1GOTzOnOpL6mjJoU+4nlvEzVwxzuPOHDq24cfey7engu0IfrZ+9Fs+icFjInY7cpowlNl0CXIWQm++ljtKVOUGUB2Bp6IwY1iwu3NVlwYVoZRCfSw5rLhccXnODYjXGMAwHF1cau4K8mkx1mD48dJnQE+y6AfrOPH+tCJ0dSDyuebNNlcE9HDmJseIGDP37Z3E/S9CCGprvLCdUYeOhXERyTevm5VJoboYq1/mhonPvY0bU4w3zVV3mXgr3Xi4SOupneUL+XfuQASRz1eGoQpAGxI8h4qWkgA66f8uPTh0hdM1JfLus9oXCtFsiYJzd+VtIZE+0JtI4ztGtm2JtgH80t2eiJTQolQihfpAXazg9fQSPH6NHsFcRplqC7emxdBZnI2F+TSjqcoX+RA78z+J2OP4ISs4ngF8USmm9/rJIRUq26XNLMjktSREBh/IwrpZWgiZqU/QzMhNriClH3n4vDHWJ03Xiw==
|
|
||||||
370
k8s/services/system-upgrade-controller/k3s-upgrade.yaml
Normal file
370
k8s/services/system-upgrade-controller/k3s-upgrade.yaml
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade
|
||||||
|
labels:
|
||||||
|
pod-security.kubernetes.io/enforce: privileged
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade
|
||||||
|
namespace: system-upgrade
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: default-controller-env
|
||||||
|
namespace: system-upgrade
|
||||||
|
data:
|
||||||
|
SYSTEM_UPGRADE_CONTROLLER_DEBUG: "false"
|
||||||
|
SYSTEM_UPGRADE_CONTROLLER_THREADS: "2"
|
||||||
|
SYSTEM_UPGRADE_CONTROLLER_LEADER_ELECT: "true"
|
||||||
|
SYSTEM_UPGRADE_JOB_ACTIVE_DEADLINE_SECONDS: "900"
|
||||||
|
SYSTEM_UPGRADE_JOB_BACKOFF_LIMIT: "99"
|
||||||
|
SYSTEM_UPGRADE_JOB_IMAGE_PULL_POLICY: Always
|
||||||
|
SYSTEM_UPGRADE_JOB_KUBECTL_IMAGE: rancher/kubectl:v1.30.3
|
||||||
|
SYSTEM_UPGRADE_JOB_PRIVILEGED: "true"
|
||||||
|
SYSTEM_UPGRADE_JOB_TTL_SECONDS_AFTER_FINISH: "900"
|
||||||
|
SYSTEM_UPGRADE_PLAN_POLLING_INTERVAL: 15m
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-controller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- batch
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- apiextensions.k8s.io
|
||||||
|
resources:
|
||||||
|
- customresourcedefinitions
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- coordination.k8s.io
|
||||||
|
resources:
|
||||||
|
- leases
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- apiGroups:
|
||||||
|
- coordination.k8s.io
|
||||||
|
resources:
|
||||||
|
- leases
|
||||||
|
resourceNames:
|
||||||
|
- system-upgrade-controller
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- upgrade.cattle.io
|
||||||
|
resources:
|
||||||
|
- plans
|
||||||
|
- plans/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- delete
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-controller-drainer
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods/eviction
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- statefulsets
|
||||||
|
- daemonsets
|
||||||
|
- replicasets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: system-upgrade-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: system-upgrade
|
||||||
|
namespace: system-upgrade
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-drainer
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: system-upgrade-controller-drainer
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: system-upgrade
|
||||||
|
namespace: system-upgrade
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-controller
|
||||||
|
namespace: system-upgrade
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- batch
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- deletecollection
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade
|
||||||
|
namespace: system-upgrade
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: system-upgrade-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: system-upgrade
|
||||||
|
namespace: system-upgrade
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: system-upgrade-controller
|
||||||
|
namespace: system-upgrade
|
||||||
|
spec:
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
upgrade.cattle.io/controller: system-upgrade-controller
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
app.kubernetes.io/name: system-upgrade-controller
|
||||||
|
upgrade.cattle.io/controller: system-upgrade-controller
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
- key: kubernetes.io/os
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- linux
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- topologyKey: kubernetes.io/hostname
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app.kubernetes.io/name
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- system-upgrade-controller
|
||||||
|
serviceAccountName: system-upgrade
|
||||||
|
tolerations:
|
||||||
|
- key: CriticalAddonsOnly
|
||||||
|
operator: Exists
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: node-role.kubernetes.io/controlplane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: node-role.kubernetes.io/etcd
|
||||||
|
operator: Exists
|
||||||
|
effect: NoExecute
|
||||||
|
containers:
|
||||||
|
- name: system-upgrade-controller
|
||||||
|
image: rancher/system-upgrade-controller:v0.14.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65534
|
||||||
|
runAsGroup: 65534
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: default-controller-env
|
||||||
|
env:
|
||||||
|
- name: SYSTEM_UPGRADE_CONTROLLER_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.labels['upgrade.cattle.io/controller']
|
||||||
|
- name: SYSTEM_UPGRADE_CONTROLLER_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: SYSTEM_UPGRADE_CONTROLLER_NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
volumeMounts:
|
||||||
|
- name: etc-ssl
|
||||||
|
mountPath: /etc/ssl
|
||||||
|
readOnly: true
|
||||||
|
- name: etc-pki
|
||||||
|
mountPath: /etc/pki
|
||||||
|
readOnly: true
|
||||||
|
- name: etc-ca-certificates
|
||||||
|
mountPath: /etc/ca-certificates
|
||||||
|
readOnly: true
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
volumes:
|
||||||
|
- name: etc-ssl
|
||||||
|
hostPath:
|
||||||
|
path: /etc/ssl
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
- name: etc-pki
|
||||||
|
hostPath:
|
||||||
|
path: /etc/pki
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
- name: etc-ca-certificates
|
||||||
|
hostPath:
|
||||||
|
path: /etc/ca-certificates
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
---
|
||||||
|
apiVersion: upgrade.cattle.io/v1
|
||||||
|
kind: Plan
|
||||||
|
metadata:
|
||||||
|
name: k3s-server
|
||||||
|
namespace: system-upgrade
|
||||||
|
labels:
|
||||||
|
k3s-upgrade: server
|
||||||
|
spec:
|
||||||
|
concurrency: 1
|
||||||
|
version: v1.36.3+k3s1
|
||||||
|
nodeSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- {key: k3s-upgrade, operator: Exists}
|
||||||
|
- {key: k3s-upgrade, operator: NotIn, values: ["disabled", "false"]}
|
||||||
|
- {key: k3os.io/mode, operator: DoesNotExist}
|
||||||
|
- {key: node-role.kubernetes.io/control-plane, operator: Exists}
|
||||||
|
serviceAccountName: system-upgrade
|
||||||
|
cordon: true
|
||||||
|
upgrade:
|
||||||
|
image: rancher/k3s-upgrade
|
||||||
|
---
|
||||||
|
apiVersion: upgrade.cattle.io/v1
|
||||||
|
kind: Plan
|
||||||
|
metadata:
|
||||||
|
name: k3s-agent
|
||||||
|
namespace: system-upgrade
|
||||||
|
labels:
|
||||||
|
k3s-upgrade: agent
|
||||||
|
spec:
|
||||||
|
concurrency: 2
|
||||||
|
version: v1.36.3+k3s1
|
||||||
|
nodeSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- {key: k3s-upgrade, operator: Exists}
|
||||||
|
- {key: k3s-upgrade, operator: NotIn, values: ["disabled", "false"]}
|
||||||
|
- {key: k3os.io/mode, operator: DoesNotExist}
|
||||||
|
- {key: node-role.kubernetes.io/control-plane, operator: DoesNotExist}
|
||||||
|
serviceAccountName: system-upgrade
|
||||||
|
prepare:
|
||||||
|
image: rancher/k3s-upgrade
|
||||||
|
args: ["prepare", "k3s-server"]
|
||||||
|
drain:
|
||||||
|
force: true
|
||||||
|
skipWaitForDeleteTimeout: 60
|
||||||
|
upgrade:
|
||||||
|
image: rancher/k3s-upgrade
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
#!/usr/bin/env -S uv run --script
|
|
||||||
# /// script
|
|
||||||
# dependencies = ["typer", "ruamel.yaml"]
|
|
||||||
# ///
|
|
||||||
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from difflib import unified_diff
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Annotated, Any, Optional
|
|
||||||
|
|
||||||
import typer
|
|
||||||
from ruamel.yaml import YAML
|
|
||||||
|
|
||||||
app = typer.Typer(help="Add or update a secret field in an existing SealedSecret")
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_file(file: Path) -> Path:
|
|
||||||
if file.is_dir():
|
|
||||||
candidate = file / "sealedsecrets.yaml"
|
|
||||||
if not candidate.is_file():
|
|
||||||
raise typer.BadParameter(f"{file} is a directory and contains no sealedsecrets.yaml")
|
|
||||||
return candidate
|
|
||||||
if not file.is_file():
|
|
||||||
raise typer.BadParameter(f"{file} does not exist")
|
|
||||||
return file
|
|
||||||
|
|
||||||
|
|
||||||
def looks_like_json(text: str) -> bool:
|
|
||||||
for char in text:
|
|
||||||
if char in (" ", "\t", "\n", "\r"):
|
|
||||||
continue
|
|
||||||
return char == "{"
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def extract_header_comments(text: str) -> tuple[str, str]:
|
|
||||||
"""Split leading comments/blank lines from the rest of the file."""
|
|
||||||
lines = text.splitlines(keepends=True)
|
|
||||||
idx = 0
|
|
||||||
while idx < len(lines):
|
|
||||||
stripped = lines[idx].strip()
|
|
||||||
if stripped == "" or stripped.startswith("#"):
|
|
||||||
idx += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return "".join(lines[:idx]), "".join(lines[idx:])
|
|
||||||
|
|
||||||
|
|
||||||
def load_documents(text: str) -> list[Any]:
|
|
||||||
if looks_like_json(text):
|
|
||||||
# JSON is valid YAML, but parse it explicitly so we can normalize it to YAML output.
|
|
||||||
return [json.loads(text)]
|
|
||||||
yaml = YAML(typ="rt")
|
|
||||||
yaml.preserve_quotes = True
|
|
||||||
return list(yaml.load_all(text))
|
|
||||||
|
|
||||||
|
|
||||||
def _represent_none(representer, data):
|
|
||||||
return representer.represent_scalar("tag:yaml.org,2002:null", "null")
|
|
||||||
|
|
||||||
|
|
||||||
def dump_documents(docs: list[Any]) -> str:
|
|
||||||
yaml = YAML(typ="rt")
|
|
||||||
yaml.default_flow_style = False
|
|
||||||
yaml.preserve_quotes = True
|
|
||||||
yaml.width = 4096
|
|
||||||
yaml.explicit_start = True
|
|
||||||
yaml.representer.add_representer(type(None), _represent_none)
|
|
||||||
from ruamel.yaml.compat import StringIO
|
|
||||||
|
|
||||||
stream = StringIO()
|
|
||||||
yaml.dump_all(docs, stream)
|
|
||||||
return stream.getvalue()
|
|
||||||
|
|
||||||
|
|
||||||
def detect_scope(doc: Any) -> str:
|
|
||||||
annotations = doc.get("metadata", {}).get("annotations", {}) or {}
|
|
||||||
for key, value in annotations.items():
|
|
||||||
if key == "sealedsecrets.bitnami.com/cluster-wide" and str(value).lower() == "true":
|
|
||||||
return "cluster-wide"
|
|
||||||
if key == "sealedsecrets.bitnami.com/namespace-wide" and str(value).lower() == "true":
|
|
||||||
return "namespace-wide"
|
|
||||||
return "strict"
|
|
||||||
|
|
||||||
|
|
||||||
def seal_value(value: str, doc: Any, cert: Optional[Path]) -> str:
|
|
||||||
metadata = doc.get("metadata", {})
|
|
||||||
spec = doc.get("spec", {})
|
|
||||||
|
|
||||||
name = metadata.get("name")
|
|
||||||
if not name:
|
|
||||||
raise typer.BadParameter("SealedSecret has no metadata.name")
|
|
||||||
|
|
||||||
namespace = metadata.get("namespace") or spec.get("template", {}).get("metadata", {}).get("namespace")
|
|
||||||
if not namespace:
|
|
||||||
raise typer.BadParameter("SealedSecret has no namespace in metadata or spec.template.metadata")
|
|
||||||
|
|
||||||
cmd = ["kubeseal", "--raw", "--from-file=/dev/stdin"]
|
|
||||||
if cert:
|
|
||||||
cmd.extend(["--cert", str(cert)])
|
|
||||||
|
|
||||||
scope = detect_scope(doc)
|
|
||||||
if scope == "cluster-wide":
|
|
||||||
cmd.extend(["--scope", "cluster-wide"])
|
|
||||||
elif scope == "namespace-wide":
|
|
||||||
cmd.extend(["--namespace", namespace, "--scope", "namespace-wide"])
|
|
||||||
else:
|
|
||||||
cmd.extend(["--namespace", namespace, "--name", name])
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
input=value,
|
|
||||||
text=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
typer.echo(f"kubeseal failed:\n{result.stderr}", err=True)
|
|
||||||
raise typer.Exit(result.returncode)
|
|
||||||
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def find_target_document(docs: list[Any], key: str, secret_name: Optional[str]) -> Any:
|
|
||||||
if secret_name:
|
|
||||||
for doc in docs:
|
|
||||||
if doc.get("metadata", {}).get("name") == secret_name:
|
|
||||||
return doc
|
|
||||||
raise typer.BadParameter(f"No SealedSecret named '{secret_name}' found in file")
|
|
||||||
|
|
||||||
if len(docs) == 1:
|
|
||||||
return docs[0]
|
|
||||||
|
|
||||||
candidates = [
|
|
||||||
doc for doc in docs
|
|
||||||
if key in (doc.get("spec", {}).get("encryptedData", {}) or {})
|
|
||||||
]
|
|
||||||
if len(candidates) == 1:
|
|
||||||
return candidates[0]
|
|
||||||
if len(candidates) > 1:
|
|
||||||
names = [doc.get("metadata", {}).get("name", "<unnamed>") for doc in candidates]
|
|
||||||
raise typer.BadParameter(
|
|
||||||
f"Key '{key}' exists in multiple SealedSecrets ({', '.join(names)}). "
|
|
||||||
"Use --secret-name to choose one."
|
|
||||||
)
|
|
||||||
|
|
||||||
names = [doc.get("metadata", {}).get("name", "<unnamed>") for doc in docs]
|
|
||||||
raise typer.BadParameter(
|
|
||||||
f"File contains multiple SealedSecrets and key '{key}' does not exist yet. "
|
|
||||||
f"Available: {', '.join(names)}. Use --secret-name to choose one."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
|
||||||
def add(
|
|
||||||
name: Annotated[str, typer.Option("--name", help="Key name in SealedSecret encryptedData")],
|
|
||||||
secret: Annotated[str, typer.Option("--secret", help="Plaintext secret value")],
|
|
||||||
file: Annotated[Path, typer.Option("--file", help="Path to sealedsecrets.yaml or its directory")],
|
|
||||||
secret_name: Annotated[
|
|
||||||
Optional[str],
|
|
||||||
typer.Option("--secret-name", help="Name of the SealedSecret resource to update (required for multi-secret files when the key is new)"),
|
|
||||||
] = None,
|
|
||||||
cert: Annotated[
|
|
||||||
Optional[Path],
|
|
||||||
typer.Option("--cert", help="Path to a kubeseal certificate for offline sealing"),
|
|
||||||
] = None,
|
|
||||||
dry_run: Annotated[bool, typer.Option("--dry-run", help="Show diff without writing")] = False,
|
|
||||||
) -> None:
|
|
||||||
target_path = resolve_file(file)
|
|
||||||
original_text = target_path.read_text()
|
|
||||||
header, body = extract_header_comments(original_text)
|
|
||||||
|
|
||||||
docs = load_documents(body)
|
|
||||||
if not docs:
|
|
||||||
raise typer.BadParameter(f"{target_path} contains no documents")
|
|
||||||
|
|
||||||
target = find_target_document(docs, name, secret_name)
|
|
||||||
sealed = seal_value(secret, target, cert)
|
|
||||||
|
|
||||||
if "spec" not in target or target["spec"] is None:
|
|
||||||
target["spec"] = {}
|
|
||||||
if "encryptedData" not in target["spec"] or target["spec"]["encryptedData"] is None:
|
|
||||||
target["spec"]["encryptedData"] = {}
|
|
||||||
|
|
||||||
old_value = target["spec"]["encryptedData"].get(name)
|
|
||||||
action = "updated" if old_value is not None else "added"
|
|
||||||
target["spec"]["encryptedData"][name] = sealed
|
|
||||||
|
|
||||||
new_text = header + dump_documents(docs)
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
diff = unified_diff(
|
|
||||||
original_text.splitlines(keepends=True),
|
|
||||||
new_text.splitlines(keepends=True),
|
|
||||||
fromfile=str(target_path),
|
|
||||||
tofile=str(target_path),
|
|
||||||
)
|
|
||||||
sys.stdout.writelines(diff)
|
|
||||||
raise typer.Exit()
|
|
||||||
|
|
||||||
backup_path = target_path.with_suffix(target_path.suffix + ".bak")
|
|
||||||
shutil.copy2(target_path, backup_path)
|
|
||||||
target_path.write_text(new_text)
|
|
||||||
|
|
||||||
typer.echo(f"{action} '{name}' in SealedSecret '{target.get('metadata', {}).get('name')}' ({target_path})")
|
|
||||||
typer.echo(f"backup saved to {backup_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app()
|
|
||||||
|
|
@ -92,19 +92,6 @@ fi
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
K8S_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
K8S_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
extract_lb_port() {
|
|
||||||
local file="$1"
|
|
||||||
local field="$2"
|
|
||||||
local selector="$3"
|
|
||||||
local value
|
|
||||||
|
|
||||||
value=$(yq -r '.. | objects | select(.type == "LoadBalancer") | '"$selector"' // empty' "$file" 2>/dev/null || true)
|
|
||||||
if [[ -n "$value" ]] && [[ "$value" != "null" ]] && [[ "$value" =~ ^[0-9]+$ ]]; then
|
|
||||||
PORTS+=("$value")
|
|
||||||
[[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found $field $value in $file"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
collect_ports() {
|
collect_ports() {
|
||||||
PORTS=()
|
PORTS=()
|
||||||
|
|
||||||
|
|
@ -117,15 +104,29 @@ collect_ports() {
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
extract_lb_port "$file" ".port" ".port"
|
lb_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | select(.port != null) | .port // empty' "$file" 2>/dev/null || true)
|
||||||
extract_lb_port "$file" "servicePortHttp" ".servicePortHttp"
|
if [[ -n "$lb_port" ]] && [[ "$lb_port" != "null" ]] && [[ "$lb_port" =~ ^[0-9]+$ ]]; then
|
||||||
extract_lb_port "$file" "servicePortHttps" ".servicePortHttps"
|
PORTS+=("$lb_port")
|
||||||
extract_lb_port "$file" "dns.port" ".dns.port"
|
[[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found .port $lb_port in $file"
|
||||||
extract_lb_port "$file" "httpPort" ".httpPort"
|
fi
|
||||||
extract_lb_port "$file" "httpsPort" ".httpsPort"
|
|
||||||
extract_lb_port "$file" "edgePort" ".edgePort"
|
service_port_http=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttp // empty' "$file" 2>/dev/null || true)
|
||||||
extract_lb_port "$file" "ldapPort" ".ldapPort"
|
if [[ -n "$service_port_http" ]] && [[ "$service_port_http" != "null" ]] && [[ "$service_port_http" =~ ^[0-9]+$ ]]; then
|
||||||
extract_lb_port "$file" "servicePort" ".servicePort"
|
PORTS+=("$service_port_http")
|
||||||
|
[[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttp $service_port_http in $file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
service_port_https=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttps // empty' "$file" 2>/dev/null || true)
|
||||||
|
if [[ -n "$service_port_https" ]] && [[ "$service_port_https" != "null" ]] && [[ "$service_port_https" =~ ^[0-9]+$ ]]; then
|
||||||
|
PORTS+=("$service_port_https")
|
||||||
|
[[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttps $service_port_https in $file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
service_dns_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .dns.port // empty' "$file" 2>/dev/null || true)
|
||||||
|
if [[ -n "$service_dns_port" ]] && [[ "$service_dns_port" != "null" ]] && [[ "$service_dns_port" =~ ^[0-9]+$ ]]; then
|
||||||
|
PORTS+=("$service_dns_port")
|
||||||
|
[[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found dns.port $service_dns_port in $file"
|
||||||
|
fi
|
||||||
done < <(find "$K8S_DIR/k8s" -name "*.yaml" -type f 2>/dev/null) || true
|
done < <(find "$K8S_DIR/k8s" -name "*.yaml" -type f 2>/dev/null) || true
|
||||||
|
|
||||||
if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then
|
if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then
|
||||||
|
|
|
||||||
293
scripts/users.py
293
scripts/users.py
|
|
@ -1,293 +0,0 @@
|
||||||
#!/usr/bin/env -S uv run --script
|
|
||||||
# /// script
|
|
||||||
# dependencies = ["ldap3", "typer", "httpx"]
|
|
||||||
# ///
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import secrets
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import typer
|
|
||||||
from ldap3 import ALL, Connection, Server, SUBTREE
|
|
||||||
|
|
||||||
LLDAP_NAMESPACE = "apps-fuku"
|
|
||||||
LLDAP_SECRET = "lldap-secrets"
|
|
||||||
LLDAP_SERVICE = "lldap.apps-fuku.svc.cluster.local"
|
|
||||||
LLDAP_LDAP_PORT = 3890
|
|
||||||
LLDAP_BASE_DN = "dc=fuku,dc=local"
|
|
||||||
LLDAP_ADMIN_USER = "uid=admin,ou=people,dc=fuku,dc=local"
|
|
||||||
LLDAP_URL = "https://ldap.fukurokuju.dev"
|
|
||||||
|
|
||||||
app = typer.Typer(help="Manage Authelia users in the LLDAP backend.")
|
|
||||||
|
|
||||||
|
|
||||||
def fail(msg: str) -> None:
|
|
||||||
print(f"ERROR: {msg}", file=sys.stderr)
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def run_kubectl(args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
||||||
return subprocess.run(
|
|
||||||
["kubectl", *args],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_secret_value(key: str) -> str:
|
|
||||||
result = run_kubectl(
|
|
||||||
[
|
|
||||||
"get",
|
|
||||||
"secret",
|
|
||||||
LLDAP_SECRET,
|
|
||||||
"-n",
|
|
||||||
LLDAP_NAMESPACE,
|
|
||||||
"-o",
|
|
||||||
f"jsonpath={{.data.{key}}}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return base64.b64decode(result.stdout).decode()
|
|
||||||
|
|
||||||
|
|
||||||
def get_configmap_value(key: str) -> str:
|
|
||||||
result = run_kubectl(
|
|
||||||
[
|
|
||||||
"get",
|
|
||||||
"configmap",
|
|
||||||
"lldap-config",
|
|
||||||
"-n",
|
|
||||||
LLDAP_NAMESPACE,
|
|
||||||
"-o",
|
|
||||||
f"jsonpath={{.data.{key}}}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def get_lldap_password() -> str:
|
|
||||||
return get_secret_value("LLDAP_LDAP_USER_PASS")
|
|
||||||
|
|
||||||
|
|
||||||
def get_lldap_hosts() -> list[str]:
|
|
||||||
result = run_kubectl(
|
|
||||||
[
|
|
||||||
"get",
|
|
||||||
"svc",
|
|
||||||
"lldap",
|
|
||||||
"-n",
|
|
||||||
LLDAP_NAMESPACE,
|
|
||||||
"-o",
|
|
||||||
"jsonpath={.status.loadBalancer.ingress[*].ip}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
ips = [ip.strip() for ip in result.stdout.split() if ip.strip()]
|
|
||||||
if not ips:
|
|
||||||
fail("could not discover lldap LoadBalancer IPs")
|
|
||||||
return ips
|
|
||||||
|
|
||||||
|
|
||||||
def lldap_connection() -> Connection:
|
|
||||||
password = get_lldap_password()
|
|
||||||
hosts = get_lldap_hosts()
|
|
||||||
last_error = None
|
|
||||||
for host in hosts:
|
|
||||||
try:
|
|
||||||
server = Server(host, port=LLDAP_LDAP_PORT, use_ssl=False, get_info=ALL)
|
|
||||||
conn = Connection(
|
|
||||||
server,
|
|
||||||
user=LLDAP_ADMIN_USER,
|
|
||||||
password=password,
|
|
||||||
auto_bind=True,
|
|
||||||
read_only=False,
|
|
||||||
)
|
|
||||||
return conn
|
|
||||||
except Exception as e:
|
|
||||||
last_error = e
|
|
||||||
continue
|
|
||||||
fail(f"could not connect to any lldap endpoint: {last_error}")
|
|
||||||
|
|
||||||
|
|
||||||
def validate_username(username: str) -> None:
|
|
||||||
if not re.match(r"^[a-zA-Z0-9_.-]+$", username):
|
|
||||||
fail(f"invalid username: {username} (allowed: a-z, 0-9, _, ., -)")
|
|
||||||
|
|
||||||
|
|
||||||
def trigger_password_reset(username: str) -> None:
|
|
||||||
url = f"{LLDAP_URL}/auth/reset/step1/{username}"
|
|
||||||
try:
|
|
||||||
response = httpx.post(url, timeout=30.0)
|
|
||||||
response.raise_for_status()
|
|
||||||
except httpx.HTTPError as e:
|
|
||||||
fail(f"failed to trigger password reset email: {e}")
|
|
||||||
|
|
||||||
print(f"Password reset email triggered for '{username}'.")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_random_password() -> str:
|
|
||||||
return secrets.token_urlsafe(32)
|
|
||||||
|
|
||||||
|
|
||||||
def get_group_id(conn: Connection, group_name: str) -> int | None:
|
|
||||||
conn.search(
|
|
||||||
f"ou=groups,{LLDAP_BASE_DN}",
|
|
||||||
f"(cn={group_name})",
|
|
||||||
SUBTREE,
|
|
||||||
attributes=["uid"],
|
|
||||||
)
|
|
||||||
if not conn.entries:
|
|
||||||
return None
|
|
||||||
uid = conn.entries[0].uid.value
|
|
||||||
try:
|
|
||||||
return int(uid)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def user_exists(conn: Connection, username: str) -> bool:
|
|
||||||
conn.search(
|
|
||||||
f"ou=people,{LLDAP_BASE_DN}",
|
|
||||||
f"(uid={username})",
|
|
||||||
SUBTREE,
|
|
||||||
attributes=["uid"],
|
|
||||||
)
|
|
||||||
return bool(conn.entries)
|
|
||||||
|
|
||||||
|
|
||||||
def email_in_use(conn: Connection, email: str) -> bool:
|
|
||||||
conn.search(
|
|
||||||
f"ou=people,{LLDAP_BASE_DN}",
|
|
||||||
f"(mail={email})",
|
|
||||||
SUBTREE,
|
|
||||||
attributes=["uid"],
|
|
||||||
)
|
|
||||||
return bool(conn.entries)
|
|
||||||
|
|
||||||
|
|
||||||
@app.command("list", help="List existing users ")
|
|
||||||
def cmd_list() -> None:
|
|
||||||
with lldap_connection() as conn:
|
|
||||||
conn.search(
|
|
||||||
f"ou=people,{LLDAP_BASE_DN}",
|
|
||||||
"(objectClass=person)",
|
|
||||||
SUBTREE,
|
|
||||||
attributes=["uid", "cn", "mail", "memberOf"],
|
|
||||||
)
|
|
||||||
if not conn.entries:
|
|
||||||
print("No users found.")
|
|
||||||
return
|
|
||||||
print(f"{'USERNAME':<20} {'DISPLAY NAME':<30} {'EMAIL':<30} GROUPS")
|
|
||||||
for entry in conn.entries:
|
|
||||||
uid = entry.uid.value if entry.uid else ""
|
|
||||||
cn = entry.cn.value if entry.cn else ""
|
|
||||||
mail = entry.mail.value if entry.mail else ""
|
|
||||||
groups = ",".join(
|
|
||||||
g.split(",")[0].replace("cn=", "") for g in entry.memberOf.values
|
|
||||||
) if entry.memberOf else ""
|
|
||||||
print(f"{uid:<20} {cn:<30} {mail:<30} {groups}")
|
|
||||||
|
|
||||||
|
|
||||||
@app.command("add", help="Add a user. Omit --password to send an invite email")
|
|
||||||
def cmd_add(
|
|
||||||
username: str = typer.Option(..., "--username", "-u", help="Username"),
|
|
||||||
displayname: str = typer.Option(..., "--displayname", "-n", help="Display name"),
|
|
||||||
password: str | None = typer.Option(None, "--password", "-p", help="Password"),
|
|
||||||
email: str = typer.Option("", "--email", "-e", help="Email address"),
|
|
||||||
groups: list[str] = typer.Option(
|
|
||||||
["users"], "--group", "-g", help="Group to add the user to (repeatable)"
|
|
||||||
),
|
|
||||||
) -> None:
|
|
||||||
validate_username(username)
|
|
||||||
|
|
||||||
invite_mode = password is None
|
|
||||||
if invite_mode and not email:
|
|
||||||
fail("--email is required when --password is omitted")
|
|
||||||
|
|
||||||
effective_email = email or f"{username}@roboces.dev"
|
|
||||||
|
|
||||||
with lldap_connection() as conn:
|
|
||||||
if user_exists(conn, username):
|
|
||||||
fail(f"user '{username}' already exists")
|
|
||||||
if email_in_use(conn, effective_email):
|
|
||||||
fail(f"email '{effective_email}' is already in use")
|
|
||||||
|
|
||||||
if invite_mode:
|
|
||||||
password = generate_random_password()
|
|
||||||
|
|
||||||
user_dn = f"uid={username},ou=people,{LLDAP_BASE_DN}"
|
|
||||||
attrs = {
|
|
||||||
"objectClass": ["inetOrgPerson", "posixAccount", "mailAccount", "person"],
|
|
||||||
"uid": username,
|
|
||||||
"cn": displayname,
|
|
||||||
"mail": effective_email,
|
|
||||||
}
|
|
||||||
if not conn.add(user_dn, attributes=attrs):
|
|
||||||
fail(f"failed to create user: {conn.result}")
|
|
||||||
|
|
||||||
if not conn.extend.standard.modify_password(user=user_dn, new_password=password):
|
|
||||||
fail(f"failed to set password: {conn.result}")
|
|
||||||
|
|
||||||
for group_name in groups:
|
|
||||||
group_id = get_group_id(conn, group_name)
|
|
||||||
if group_id is None:
|
|
||||||
print(f" WARNING: group '{group_name}' not found, skipping")
|
|
||||||
continue
|
|
||||||
if not conn.add_user_to_group(user_dn, group_id):
|
|
||||||
print(f" WARNING: could not add to group '{group_name}': {conn.result}")
|
|
||||||
else:
|
|
||||||
print(f" Added to group '{group_name}'.")
|
|
||||||
|
|
||||||
if invite_mode:
|
|
||||||
trigger_password_reset(username)
|
|
||||||
print(f"User '{username}' created and invite email sent to '{effective_email}'.")
|
|
||||||
else:
|
|
||||||
print(f"User '{username}' created.")
|
|
||||||
|
|
||||||
|
|
||||||
@app.command("remove", help="Remove a user")
|
|
||||||
def cmd_remove(
|
|
||||||
username: str = typer.Option(..., "--username", "-u", help="Username"),
|
|
||||||
) -> None:
|
|
||||||
validate_username(username)
|
|
||||||
with lldap_connection() as conn:
|
|
||||||
user_dn = f"uid={username},ou=people,{LLDAP_BASE_DN}"
|
|
||||||
conn.search(user_dn, "(objectClass=*)", SUBTREE, attributes=["uid"])
|
|
||||||
if not conn.entries:
|
|
||||||
fail(f"user '{username}' not found")
|
|
||||||
if not conn.delete(user_dn):
|
|
||||||
fail(f"failed to delete user: {conn.result}")
|
|
||||||
print(f"User '{username}' removed from LLDAP.")
|
|
||||||
|
|
||||||
|
|
||||||
@app.command("reset-password", help="Trigger a password reset email for an existing user")
|
|
||||||
def cmd_reset_password(
|
|
||||||
username: str = typer.Option(..., "--username", "-u", help="Username"),
|
|
||||||
confirm: bool = typer.Option(
|
|
||||||
True,
|
|
||||||
"--confirm/--no-confirm",
|
|
||||||
help="Prompt for confirmation before sending the reset email",
|
|
||||||
),
|
|
||||||
) -> None:
|
|
||||||
validate_username(username)
|
|
||||||
with lldap_connection() as conn:
|
|
||||||
if not user_exists(conn=conn, username=username):
|
|
||||||
fail(f"user '{username}' not found")
|
|
||||||
|
|
||||||
if confirm:
|
|
||||||
typer.confirm(
|
|
||||||
f"Send password reset email to '{username}'?",
|
|
||||||
abort=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
trigger_password_reset(username)
|
|
||||||
print(f"Password reset email triggered for '{username}'.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault("PYTHONUNBUFFERED", "1")
|
|
||||||
sys.stdout.reconfigure(line_buffering=True)
|
|
||||||
app()
|
|
||||||
|
|
@ -114,26 +114,12 @@ resource "adguard_rewrite" "vault" {
|
||||||
answer = "192.168.1.12"
|
answer = "192.168.1.12"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "adguard_rewrite" "authelia-roboces" {
|
resource "adguard_rewrite" "pocketid" {
|
||||||
domain = "auth.roboces.dev"
|
domain = "auth.roboces.dev"
|
||||||
answer = "192.168.1.12"
|
answer = "192.168.1.12"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "adguard_rewrite" "authelia" {
|
|
||||||
domain = "auth.fuku"
|
|
||||||
answer = "192.168.1.12"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "adguard_rewrite" "dns" {
|
resource "adguard_rewrite" "dns" {
|
||||||
domain = "dns.fuku"
|
domain = "dns.fuku"
|
||||||
answer = "192.168.1.12"
|
answer = "192.168.1.12"
|
||||||
}
|
}
|
||||||
resource "adguard_rewrite" "ldap" {
|
|
||||||
domain = "ldap.fuku"
|
|
||||||
answer = "192.168.1.12"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "adguard_rewrite" "audiobooks" {
|
|
||||||
domain = "audiobooks.fuku"
|
|
||||||
answer = "192.168.1.12"
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue