A lightweight, multi-tenant LDAP authentication service that generates JWT tokens for users authenticated via LDAP.
ldap-jwt-generator is a Go-based authentication microservice that bridges LDAP directory services with JWT-based authentication systems. It validates user credentials against LDAP, retrieves user group memberships, and issues signed JWT tokens containing user identity and authorization claims.
This service is part of the Kubi project ecosystem and is designed for Kubernetes-native deployments where LDAP authentication needs to be exposed via standardized JWT tokens.
- Multi-tenant LDAP Support: Configure different LDAP search parameters for multiple tenants
- JWT Token Generation: Issue JWT tokens signed with ECDSA-P512 algorithm
- Group-based Authorization: Fetch and validate user group memberships from LDAP
- Prometheus Metrics: Export authentication and LDAP operation metrics
- Kubernetes Integration: Native support for Kubernetes deployments with ConfigMaps and Secrets
- Secure by Default: TLS support, credential validation, and secure token signing
The service implements an HTTP middleware chain that processes authentication requests:
┌─────────────────────────────────────────────────────────────────┐
│ HTTP Request │
│ Authorization: Basic <credentials> │
│ Tenant-Id: <tenant> │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ WithTenantConfig │ ← Load tenant LDAP config
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ WithBasicAuth │ ← Validate credentials (LDAP Bind)
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ WithGroupEnrichment │ ← Fetch user groups (LDAP Search)
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ GenerateJWT │ ← Create signed JWT token
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ HTTP 201 Response │
│ Body: JWT Token │
└──────────────────────┘
- Go: 1.26 or later
- Docker: For building and testing
- mise: Task automation tool (installation guide)
- LDAP Server: OpenLDAP, Active Directory, or compatible LDAP service
- curl: For testing API requests
# Clone the repository
git clone https://github.com/ca-gip/ldap-jwt-generator.git
cd ldap-jwt-generator
# Install dependencies (mise will install Go and golangci-lint)
mise install
# Build the binary and Docker image
mise task run builddocker pull ghcr.io/ca-gip/ldap-jwt-generator:latestThe service requires the following environment variables:
| Variable | Description | Example | Required | Default |
|---|---|---|---|---|
LDAP_SERVER |
LDAP server hostname or IP | ldap.example.com |
Yes | - |
LDAP_PORT |
LDAP server port | 389 |
No | 389 |
LDAP_BINDDN |
Service account DN for LDAP binds | cn=admin,dc=example,dc=org |
Yes | - |
LDAP_PASSWD |
Service account password | secretpassword |
Yes | - |
LDAP_USE_SSL |
Enable SSL connection | true |
No | false |
LDAP_START_TLS |
Enable StartTLS (use with port 389) | true |
No | false |
LDAP_SKIP_TLS_VERIFICATION |
Skip TLS certificate verification | true |
No | true |
LDAP_PAGE_SIZE |
LDAP paging size for searches | 1000 |
No | 1000 |
JWT_ISSUER_FQDN |
JWT issuer FQDN for token claims | auth.example.com |
Yes | - |
TOKEN_LIFETIME |
JWT token expiration duration | 4h |
No | 4h |
Each tenant requires a JSON configuration file that defines LDAP search parameters. These files should be mounted at /etc/ldap-jwt-generator/ldap-configs/.
File naming convention: <tenant-id>.json
Example configuration (tenant1.json):
{
"userBase": "ou=Users,dc=example,dc=org",
"userFilter": "(cn=%s)",
"groupSources": [
{
"base": "ou=Groups,dc=example,dc=org",
"filter": "(&(objectClass=groupOfNames)(member=%s))"
}
]
}Configuration fields:
userBase: Base DN for user searchesuserFilter: LDAP filter for finding users (use%sas username placeholder)groupSources: Array of group search configurationsbase: Base DN for group searchesfilter: LDAP filter for finding groups (use%sas user DN placeholder)
Generate ECDSA-P512 key pair for JWT signing:
# Generate private key
openssl ecparam -genkey -name secp521r1 -noout -out ecdsa-private-key.pem
# Generate public key
openssl ec -in ecdsa-private-key.pem -pubout -out ecdsa-public-key.pemMount these keys at /etc/ldap-jwt-generator/signing-keys/:
ecdsa-private-key.pem- Private key for signing tokensecdsa-public-key.pem- Public key for token verification
Endpoint: GET /token
Request Headers:
Authorization: Basic <base64(username:password)>- User credentialsTenant-Id: <tenant-id>- Tenant identifier matching configuration file name
Example Request:
# Authenticate as user "admin-kube1" with tenant "tenant1"
curl -X GET http://localhost:8080/token \
-H "Authorization: Basic $(echo -n 'admin-kube1:password123' | base64)" \
-H "Tenant-Id: tenant1"Success Response:
HTTP/1.1 201 Created
Content-Type: text/plain
eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ta3ViZTEiLCJjb250YWN0IjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ1c2VyRE4iOiJjbj1hZG1pbi1rdWJlMSxvdT1Vc2VycyxkYz1leGFtcGxlLGRjPW9yZyIsInRlbmFudCI6InRlbmFudDEiLCJncm91cHMiOlsia3ViZS1hZG1pbnMiXSwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsInN1YiI6ImFkbWluLWt1YmUxIiwiYXVkIjpbImF1dGguZXhhbXBsZS5jb20iXSwiZXhwIjoxNzQwMDAwMDAwLCJuYmYiOjE3Mzk5ODU2MDAsImlhdCI6MTczOTk4NTYwMCwianRpIjoiMTIzNDU2NzgtYWJjZC0xMjM0LWFiY2QtMTIzNDU2Nzg5YWJjIn0.signature
Error Responses:
400 Bad Request- Missing Tenant-Id header or invalid request401 Unauthorized- Invalid credentials or authentication failed403 Forbidden- User does not belong to any groups404 Not Found- Tenant configuration not found500 Internal Server Error- Server-side error
The issued JWT token contains the following claims:
Custom Claims:
user- Username (e.g., "admin-kube1")contact- User email addressuserDN- LDAP Distinguished Nametenant- Tenant identifiergroups- Array of group names user belongs to
Standard JWT Claims:
iss- Issuer (fromJWT_ISSUER_FQDN)sub- Subject (username)aud- Audience (array with issuer FQDN)exp- Expiration timenbf- Not before timeiat- Issued at timejti- JWT ID (unique identifier)
Example decoded token:
{
"user": "admin-kube1",
"contact": "admin@example.com",
"userDN": "cn=admin-kube1,ou=Users,dc=example,dc=org",
"tenant": "tenant1",
"groups": ["kube-admins", "developers"],
"iss": "auth.example.com",
"sub": "admin-kube1",
"aud": ["auth.example.com"],
"exp": 1740000000,
"nbf": 1739985600,
"iat": 1739985600,
"jti": "12345678-abcd-1234-abcd-123456789abc"
}Endpoint: GET /metrics
Exports the following metrics:
- LDAP authentication attempts (success/failure)
- LDAP query latencies
- JWT token generation counts
- HTTP request durations
# Run all tests (linting, unit, e2e)
mise task run test:all
# Build the project
mise task run build
# Start local LDAP server for development
mise task run dev:ldap:start
# Start local API server
mise task run dev:api:start
# View logs
mise task run dev:logs
# Stop services
mise task run dev:api:stop
mise task run dev:ldap:stop| Task | Description |
|---|---|
build |
Build binary and Docker image |
test:lint |
Run code linters (fmt, vet, golangci-lint) |
test:unit |
Run unit tests with coverage |
test:e2e |
Run end-to-end tests with LDAP containers |
test:all |
Run all tests (lint + unit + e2e) |
dev:ldap:start |
Start OpenLDAP development server |
dev:ldap:stop |
Stop OpenLDAP server |
dev:api:start |
Start API server for local testing |
dev:api:stop |
Stop API server |
dev:logs |
View container logs |
clean |
Remove build artifacts |
tag:git |
Tag git repository |
For detailed contribution guidelines, see CONTRIBUTING.md.
docker run -d \
--name ldap-jwt-generator \
-p 8080:8080 \
-e LDAP_SERVER="ldap.example.com" \
-e LDAP_PORT="389" \
-e LDAP_BINDDN="cn=admin,dc=example,dc=org" \
-e LDAP_PASSWD="secretpassword" \
-e JWT_ISSUER_FQDN="auth.example.com" \
-e TOKEN_LIFETIME="4h" \
-v /path/to/tenant-configs:/etc/ldap-jwt-generator/ldap-configs:ro \
-v /path/to/signing-keys:/etc/ldap-jwt-generator/signing-keys:ro \
ghcr.io/ca-gip/ldap-jwt-generator:latestThe service includes Kubernetes deployment manifests in the deployments/ directory.
Required resources:
- ConfigMap: Mount tenant configuration JSON files
- Secret: Mount ECDSA signing keys
- Deployment: Application pods
- Service: Internal service endpoint
- ServiceMonitor: Prometheus metrics scraping (optional)
Example ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: ldap-jwt-tenant-configs
data:
tenant1.json: |
{
"userBase": "ou=Users,dc=example,dc=org",
"userFilter": "(cn=%s)",
"groupSources": [
{
"base": "ou=Groups,dc=example,dc=org",
"filter": "(&(objectClass=groupOfNames)(member=%s))"
}
]
}Example Secret:
apiVersion: v1
kind: Secret
metadata:
name: ldap-jwt-signing-keys
type: Opaque
data:
ecdsa-private-key.pem: <base64-encoded-private-key>
ecdsa-public-key.pem: <base64-encoded-public-key>Refer to the deployments/ directory for complete examples.
The project uses a comprehensive testing strategy:
- Unit Tests: Fast, isolated tests for individual components
- E2E Tests: Full integration tests with real LDAP server
- Test Fixtures: Pre-configured LDAP data and tenant configurations
Run tests with:
# All tests (recommended before committing)
mise task run test:all
# Individual test suites
mise task run test:lint # Code quality checks
mise task run test:unit # Unit tests only
mise task run test:e2e # Integration tests onlyThe E2E test suite automatically:
- Spins up an OpenLDAP container
- Loads test data from
test/fixtures/ldif/test-data.ldif - Builds and deploys the API container
- Runs authentication flow tests
- Tears down all test infrastructure
Contributions are welcome! Please see CONTRIBUTING.md for development guidelines.
Before submitting a PR:
- Write tests for your changes
- Run
mise task run test:allto ensure all tests pass - Follow the existing code patterns and conventions
This project is licensed under the terms specified in the LICENSE file.
- Kubi - Kubernetes User Management