feat: add lldap chart and app

This commit is contained in:
cătălin 2026-08-17 09:21:51 +02:00
commit f14bb601ce
No known key found for this signature in database
16 changed files with 607 additions and 0 deletions

55
k8s/argo-apps/lldap.yaml Normal file
View file

@ -0,0 +1,55 @@
---
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: oci://git.roboces.dev/catalin/fukuops
chart: lldap
targetRevision: 1.0.0
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.fuku"
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

View file

@ -0,0 +1,13 @@
# Patterns to ignore when building packages.
# This supports shell glob matching.
*.tgz
.env
.git
.git_cache
.idea
*.md
docs/*
tmp/*
vendor/*
.travis.yml
.testignore

View file

@ -0,0 +1,15 @@
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

150
k8s/charts/lldap/README.md Normal file
View file

@ -0,0 +1,150 @@
# 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.**

View file

@ -0,0 +1,37 @@
{{/* 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 }}

View file

@ -0,0 +1,33 @@
{{- 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 }}

View file

@ -0,0 +1,111 @@
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 }}

View file

@ -0,0 +1,41 @@
{{- 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 }}

View file

@ -0,0 +1,18 @@
{{- 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 }}

View file

@ -0,0 +1,17 @@
{{- 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 }}

View file

@ -0,0 +1,19 @@
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 }}

View file

@ -0,0 +1,90 @@
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

View file

@ -26,3 +26,5 @@ spec:
- https://repo.helmforge.dev - https://repo.helmforge.dev
- https://harish2k01.github.io/helm-charts/ - https://harish2k01.github.io/helm-charts/
- git.roboces.dev/catalin/fukuops - git.roboces.dev/catalin/fukuops
- https://kubereboot.github.io/charts
- https://charts.goauthentik.io/

View file

@ -25,3 +25,4 @@ 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

View file

@ -16,4 +16,5 @@ 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

View file

@ -123,3 +123,7 @@ 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"
}