Initial Commit
This commit is contained in:
commit
c556664056
23
.env.example
Normal file
23
.env.example
Normal file
@ -0,0 +1,23 @@
|
||||
# Required
|
||||
LDAP_DOMAIN=example.com
|
||||
LDAP_ORGANISATION=Example ORG
|
||||
LDAP_ADMIN_PASSWORD=Yourpasswordhere
|
||||
|
||||
# Optional - TLS
|
||||
LDAP_TLS_ENABLED=true
|
||||
LDAP_TLS_CERT_FILE=/certs/ldap.crt
|
||||
LDAP_TLS_KEY_FILE=/certs/ldap.key
|
||||
LDAP_TLS_CA_FILE=/certs/ca.crt
|
||||
|
||||
# Optional - Service accounts
|
||||
LDAP_CREATE_SERVICE_ACCOUNTS=true
|
||||
# If not set, random passwords will be generated and saved to /var/lib/openldap/service-passwords.txt
|
||||
# LDAP_SERVICE_KEYCLOAK_PASSWORD=
|
||||
# LDAP_SERVICE_NEXTCLOUD_PASSWORD=
|
||||
# LDAP_SERVICE_GITEA_PASSWORD=
|
||||
# LDAP_SERVICE_POSTFIX_PASSWORD=
|
||||
# LDAP_SERVICE_DOVECOT_PASSWORD=
|
||||
# LDAP_SERVICE_SSSD_PASSWORD=
|
||||
|
||||
# Optional - Logging
|
||||
LDAP_LOG_LEVEL=256
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Env files usualy contain credentials, we do not want to store those in git
|
||||
.env
|
||||
|
||||
# No Claude code stuff in here either
|
||||
.claude
|
||||
|
||||
49
Dockerfile
Normal file
49
Dockerfile
Normal file
@ -0,0 +1,49 @@
|
||||
FROM alpine:3.23
|
||||
|
||||
LABEL maintainer="WeBuildYourCloud"
|
||||
LABEL description="Enterprise OpenLDAP container with rfc2307bis, memberOf, and custom schemas"
|
||||
|
||||
# Install OpenLDAP and required packages
|
||||
RUN apk add --no-cache \
|
||||
openldap \
|
||||
openldap-clients \
|
||||
openldap-back-mdb \
|
||||
openldap-overlay-memberof \
|
||||
openldap-overlay-refint \
|
||||
openldap-overlay-unique \
|
||||
openldap-overlay-ppolicy \
|
||||
openssl \
|
||||
argon2 \
|
||||
&& mkdir -p /var/lib/openldap/openldap-data \
|
||||
&& mkdir -p /etc/openldap/slapd.d \
|
||||
&& mkdir -p /run/openldap \
|
||||
&& mkdir -p /certs \
|
||||
&& chown -R ldap:ldap /var/lib/openldap \
|
||||
&& chown -R ldap:ldap /etc/openldap/slapd.d \
|
||||
&& chown -R ldap:ldap /run/openldap
|
||||
|
||||
# Copy custom schemas
|
||||
COPY schema/*.schema /etc/openldap/schema/
|
||||
|
||||
# Copy initialization scripts
|
||||
COPY scripts/ /scripts/
|
||||
RUN chmod +x /scripts/*.sh
|
||||
|
||||
# Copy LDIF templates
|
||||
COPY ldif/ /ldif/
|
||||
|
||||
# Copy entrypoint
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 389 636
|
||||
|
||||
# Volumes for persistence
|
||||
VOLUME ["/var/lib/openldap/openldap-data", "/etc/openldap/slapd.d", "/certs"]
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD ldapsearch -x -H ldap://localhost -b "" -s base "objectClass=*" || exit 1
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
669
README.md
Normal file
669
README.md
Normal file
@ -0,0 +1,669 @@
|
||||
# OpenLDAP Container Operations Guide
|
||||
|
||||
## Introduction
|
||||
|
||||
This document provides operational guidance for deploying and managing the Enterprise OpenLDAP container. It explains not just the how, but also the what and why behind each configuration decision.
|
||||
|
||||
### What This Container Provides
|
||||
|
||||
This is a purpose-built OpenLDAP container designed to serve as a central identity provider for an enterprise environment. It provides:
|
||||
|
||||
- **Centralized user authentication** for all services (Keycloak, Nextcloud, Gitea, mail, etc.)
|
||||
- **POSIX account support** for Linux systems via SSSD integration
|
||||
- **Group-based access control** with automatic membership tracking
|
||||
- **Service account isolation** so each application has its own credentials with limited permissions
|
||||
|
||||
### Why OpenLDAP?
|
||||
|
||||
LDAP (Lightweight Directory Access Protocol) remains the standard for centralized identity management because:
|
||||
|
||||
1. **Universal support** - Nearly every enterprise application supports LDAP authentication
|
||||
2. **Hierarchical organization** - Natural fit for organizational structures (users, groups, departments)
|
||||
3. **Fine-grained access control** - Different applications can have different levels of access
|
||||
4. **Proven reliability** - Decades of production use in demanding environments
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**RFC2307bis instead of NIS schema**: Traditional LDAP setups use the NIS schema where `posixGroup` is a STRUCTURAL class. This prevents combining POSIX groups with other group types. We use RFC2307bis where `posixGroup` is AUXILIARY, allowing groups to have both POSIX attributes (gidNumber) and member tracking (member attribute). This is essential for the memberOf overlay to work correctly.
|
||||
|
||||
**Pre-configured overlays**: The container includes four overlays that are critical for enterprise use:
|
||||
- **memberOf** - Automatically maintains reverse group membership on user objects
|
||||
- **refint** - Maintains referential integrity when users are deleted
|
||||
- **unique** - Prevents duplicate UIDs, email addresses, and ID numbers
|
||||
- **ppolicy** - Enforces password complexity and expiration policies
|
||||
|
||||
**Service accounts**: Instead of sharing the admin password with applications, each service gets its own account with read-only access to the directory. This follows the principle of least privilege and provides audit trails.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Directory Structure
|
||||
|
||||
The container creates a directory tree optimized for enterprise use:
|
||||
|
||||
```
|
||||
dc=example,dc=com ← Your organization's base
|
||||
│
|
||||
├── ou=People ← All user accounts live here
|
||||
│ └── uid=jdoe ← Individual user
|
||||
│
|
||||
├── ou=Groups ← Authorization and access groups
|
||||
│ ├── cn=admins ← LDAP administrators
|
||||
│ └── cn=developers ← Example application group
|
||||
│
|
||||
├── ou=Services ← Application service accounts
|
||||
│ ├── cn=keycloak ← For Keycloak LDAP federation
|
||||
│ ├── cn=nextcloud ← For Nextcloud user backend
|
||||
│ ├── cn=gitea ← For Gitea authentication
|
||||
│ ├── cn=postfix ← For mail routing lookups
|
||||
│ ├── cn=dovecot ← For mail authentication
|
||||
│ └── cn=sssd ← For Linux PAM/NSS
|
||||
│
|
||||
├── ou=Domains ← Virtual mail domains (for mail server)
|
||||
│
|
||||
├── ou=Policies ← Password and security policies
|
||||
│ └── cn=default ← Default password policy
|
||||
│
|
||||
└── ou=Kerberos ← Reserved for Kerberos integration
|
||||
```
|
||||
|
||||
**Why this structure?**
|
||||
|
||||
- **Separate OUs for each purpose** makes ACLs (access control lists) simpler and more secure
|
||||
- **Service accounts in their own OU** allows restricting what parts of the tree they can access
|
||||
- **Policies OU** keeps password policies separate from user data
|
||||
- **Domains OU** is specifically for mail server virtual domain configuration
|
||||
|
||||
### How Authentication Works
|
||||
|
||||
When an application authenticates a user:
|
||||
|
||||
1. Application connects to LDAP using its service account (e.g., `cn=keycloak,ou=Services,...`)
|
||||
2. Application searches for the user in `ou=People` by uid or email
|
||||
3. Application attempts to bind (authenticate) as that user with their password
|
||||
4. LDAP verifies the password and returns success/failure
|
||||
5. Application can then query the user's groups via the `memberOf` attribute
|
||||
|
||||
The service account never sees user passwords - it only searches for users. The actual password verification happens when the user's DN is used for a bind operation.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before deploying, ensure you have:
|
||||
|
||||
- Docker installed and running
|
||||
- Decided on your domain name (this determines your base DN)
|
||||
- Generated a strong admin password
|
||||
- (Optional) TLS certificates if you need encrypted connections
|
||||
|
||||
### Step 1: Basic Deployment
|
||||
|
||||
The simplest deployment requires just three pieces of information:
|
||||
|
||||
```bash
|
||||
docker run -d --name openldap \
|
||||
-e LDAP_DOMAIN=example.com \
|
||||
-e LDAP_ORGANISATION="Example Corporation" \
|
||||
-e LDAP_ADMIN_PASSWORD=your-secure-password \
|
||||
-p 389:389 \
|
||||
enterprise-openldap:latest
|
||||
```
|
||||
|
||||
**What happens on first run:**
|
||||
|
||||
1. The container detects this is a fresh start (no existing database)
|
||||
2. It generates the base DN from your domain (`example.com` → `dc=example,dc=com`)
|
||||
3. Creates the cn=config database with your settings
|
||||
4. Loads all required schemas in the correct order
|
||||
5. Configures the overlays (memberOf, refint, unique, ppolicy)
|
||||
6. Creates the directory structure (OUs)
|
||||
7. Sets up ACLs to protect sensitive data
|
||||
8. Optionally creates service accounts
|
||||
9. Starts the LDAP server
|
||||
|
||||
On subsequent restarts, steps 2-8 are skipped - the existing database is used.
|
||||
|
||||
### Step 2: Verify the Deployment
|
||||
|
||||
Test that the server is responding:
|
||||
|
||||
```bash
|
||||
# Anonymous query to verify the server is up
|
||||
ldapsearch -x -H ldap://localhost:389 -b "" -s base "(objectClass=*)"
|
||||
```
|
||||
|
||||
Test admin authentication:
|
||||
|
||||
```bash
|
||||
ldapsearch -x -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=example,dc=com" \
|
||||
-w "your-secure-password" \
|
||||
-b "dc=example,dc=com" "(objectClass=organizationalUnit)"
|
||||
```
|
||||
|
||||
You should see the OUs (People, Groups, Services, etc.) listed.
|
||||
|
||||
### Step 3: Enable Service Accounts (Recommended)
|
||||
|
||||
Service accounts allow applications to connect with limited, auditable credentials:
|
||||
|
||||
```bash
|
||||
docker run -d --name openldap \
|
||||
-e LDAP_DOMAIN=example.com \
|
||||
-e LDAP_ORGANISATION="Example Corporation" \
|
||||
-e LDAP_ADMIN_PASSWORD=your-secure-password \
|
||||
-e LDAP_CREATE_SERVICE_ACCOUNTS=true \
|
||||
-p 389:389 \
|
||||
enterprise-openldap:latest
|
||||
```
|
||||
|
||||
The generated passwords are saved to `/var/lib/openldap/service-passwords.txt` inside the container. Retrieve them:
|
||||
|
||||
```bash
|
||||
docker exec openldap cat /var/lib/openldap/service-passwords.txt
|
||||
```
|
||||
|
||||
**Important**: Save these passwords securely, then delete the file:
|
||||
|
||||
```bash
|
||||
docker exec openldap rm /var/lib/openldap/service-passwords.txt
|
||||
```
|
||||
|
||||
### Step 4: Add Persistence
|
||||
|
||||
Without volumes, your data is lost when the container is removed. For production, always use volumes:
|
||||
|
||||
```bash
|
||||
docker run -d --name openldap \
|
||||
-v openldap-data:/var/lib/openldap/openldap-data \
|
||||
-v openldap-config:/etc/openldap/slapd.d \
|
||||
-e LDAP_DOMAIN=example.com \
|
||||
-e LDAP_ORGANISATION="Example Corporation" \
|
||||
-e LDAP_ADMIN_PASSWORD=your-secure-password \
|
||||
-e LDAP_CREATE_SERVICE_ACCOUNTS=true \
|
||||
-p 389:389 \
|
||||
enterprise-openldap:latest
|
||||
```
|
||||
|
||||
**Volume purposes:**
|
||||
|
||||
| Volume | Contains | Why it matters |
|
||||
|--------|----------|----------------|
|
||||
| `/var/lib/openldap/openldap-data` | The actual LDAP database (MDB format) | All your users, groups, and data |
|
||||
| `/etc/openldap/slapd.d` | The cn=config configuration | Schema, overlays, ACLs, indexes |
|
||||
|
||||
### Step 5: Enable TLS (Production)
|
||||
|
||||
For production environments, you should enable TLS to encrypt connections:
|
||||
|
||||
```bash
|
||||
docker run -d --name openldap \
|
||||
-v openldap-data:/var/lib/openldap/openldap-data \
|
||||
-v openldap-config:/etc/openldap/slapd.d \
|
||||
-v /path/to/certs:/certs:ro \
|
||||
-e LDAP_DOMAIN=example.com \
|
||||
-e LDAP_ORGANISATION="Example Corporation" \
|
||||
-e LDAP_ADMIN_PASSWORD=your-secure-password \
|
||||
-e LDAP_TLS_ENABLED=true \
|
||||
-p 389:389 -p 636:636 \
|
||||
enterprise-openldap:latest
|
||||
```
|
||||
|
||||
The `/certs` directory must contain:
|
||||
- `ldap.crt` - Server certificate
|
||||
- `ldap.key` - Private key (readable only by root/ldap user)
|
||||
- `ca.crt` - CA certificate (for client verification)
|
||||
|
||||
**Port 389 vs 636:**
|
||||
- Port 389 (LDAP) - Unencrypted, but supports STARTTLS upgrade
|
||||
- Port 636 (LDAPS) - TLS from the start, like HTTPS
|
||||
|
||||
Most modern applications support STARTTLS on port 389, which is generally preferred.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
These must be set for the container to start:
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `LDAP_DOMAIN` | Your organization's domain. Converted to base DN automatically. | `example.com` → `dc=example,dc=com` |
|
||||
| `LDAP_ORGANISATION` | Human-readable organization name, stored in the base entry. | `Example Corporation` |
|
||||
| `LDAP_ADMIN_PASSWORD` | Password for the directory administrator account. **Use cleartext** - the container hashes it automatically with `slappasswd`. | `Str0ng!Passw0rd` |
|
||||
|
||||
### Optional Environment Variables
|
||||
|
||||
#### General Settings
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `LDAP_BASE_DN` | Generated from domain | Override if you need a non-standard base DN |
|
||||
| `LDAP_CONFIG_PASSWORD` | Random | Password for cn=config administrative access. Only needed for advanced configuration changes. |
|
||||
| `LDAP_LOG_LEVEL` | `256` | Controls logging verbosity. `256` logs connections and operations. See [Log Levels](#understanding-log-levels). |
|
||||
|
||||
#### TLS Settings
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `LDAP_TLS_ENABLED` | `true` | Master switch for TLS. Set `false` to disable completely. |
|
||||
| `LDAP_TLS_CERT_FILE` | `/certs/ldap.crt` | Path to server certificate inside container |
|
||||
| `LDAP_TLS_KEY_FILE` | `/certs/ldap.key` | Path to private key inside container |
|
||||
| `LDAP_TLS_CA_FILE` | `/certs/ca.crt` | Path to CA certificate for client verification |
|
||||
| `LDAP_TLS_VERIFY_CLIENT` | `try` | Client cert policy: `never` (don't ask), `try` (optional), `demand` (required) |
|
||||
|
||||
#### Service Account Settings
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `LDAP_CREATE_SERVICE_ACCOUNTS` | `false` | Set `true` to create pre-configured service accounts |
|
||||
| `LDAP_SERVICE_KEYCLOAK_PASSWORD` | Random | Explicit password for Keycloak account |
|
||||
| `LDAP_SERVICE_NEXTCLOUD_PASSWORD` | Random | Explicit password for Nextcloud account |
|
||||
| `LDAP_SERVICE_GITEA_PASSWORD` | Random | Explicit password for Gitea account |
|
||||
| `LDAP_SERVICE_POSTFIX_PASSWORD` | Random | Explicit password for Postfix account |
|
||||
| `LDAP_SERVICE_DOVECOT_PASSWORD` | Random | Explicit password for Dovecot account |
|
||||
| `LDAP_SERVICE_SSSD_PASSWORD` | Random | Explicit password for SSSD account |
|
||||
|
||||
**When to set explicit passwords:** If you're deploying with configuration management (Ansible, Terraform) and need predictable credentials, set them explicitly. For manual deployments, random passwords are more secure.
|
||||
|
||||
---
|
||||
|
||||
## Day-to-Day Operations
|
||||
|
||||
### Adding Users
|
||||
|
||||
Users belong in `ou=People`. Here's a complete user entry:
|
||||
|
||||
```ldif
|
||||
dn: uid=jdoe,ou=People,dc=example,dc=com
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
uid: jdoe
|
||||
cn: John Doe
|
||||
sn: Doe
|
||||
givenName: John
|
||||
mail: john.doe@example.com
|
||||
uidNumber: 10001
|
||||
gidNumber: 10000
|
||||
homeDirectory: /home/jdoe
|
||||
loginShell: /bin/bash
|
||||
userPassword: {SSHA}hashed-password
|
||||
```
|
||||
|
||||
**Object classes explained:**
|
||||
- `inetOrgPerson` - Standard LDAP person with email, phone, etc.
|
||||
- `posixAccount` - UNIX account attributes (uid/gid numbers, home directory, shell)
|
||||
- `shadowAccount` - Password aging and expiration
|
||||
|
||||
Add the user:
|
||||
|
||||
```bash
|
||||
ldapadd -x -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=example,dc=com" \
|
||||
-w "admin-password" \
|
||||
-f user.ldif
|
||||
```
|
||||
|
||||
### Adding Groups
|
||||
|
||||
Groups use `groupOfMembers` (for member tracking) combined with `posixGroup` (for UNIX gidNumber):
|
||||
|
||||
```ldif
|
||||
dn: cn=developers,ou=Groups,dc=example,dc=com
|
||||
objectClass: groupOfMembers
|
||||
objectClass: posixGroup
|
||||
cn: developers
|
||||
gidNumber: 10001
|
||||
description: Development team
|
||||
member: uid=jdoe,ou=People,dc=example,dc=com
|
||||
```
|
||||
|
||||
**Why groupOfMembers instead of groupOfNames?**
|
||||
|
||||
`groupOfNames` requires at least one member - you can't have an empty group. `groupOfMembers` allows empty groups, which is often needed when setting up access control before adding members.
|
||||
|
||||
### Checking Group Membership
|
||||
|
||||
Thanks to the memberOf overlay, you can see a user's groups directly:
|
||||
|
||||
```bash
|
||||
ldapsearch -x -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=example,dc=com" \
|
||||
-w "admin-password" \
|
||||
-b "uid=jdoe,ou=People,dc=example,dc=com" \
|
||||
memberOf
|
||||
```
|
||||
|
||||
This returns all groups the user belongs to, without having to search each group.
|
||||
|
||||
### Modifying Entries
|
||||
|
||||
To change an attribute:
|
||||
|
||||
```ldif
|
||||
dn: uid=jdoe,ou=People,dc=example,dc=com
|
||||
changetype: modify
|
||||
replace: mail
|
||||
mail: j.doe@example.com
|
||||
```
|
||||
|
||||
```bash
|
||||
ldapmodify -x -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=example,dc=com" \
|
||||
-w "admin-password" \
|
||||
-f modify.ldif
|
||||
```
|
||||
|
||||
### Deleting Entries
|
||||
|
||||
```bash
|
||||
ldapdelete -x -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=example,dc=com" \
|
||||
-w "admin-password" \
|
||||
"uid=jdoe,ou=People,dc=example,dc=com"
|
||||
```
|
||||
|
||||
The refint overlay automatically removes the user from any groups they belonged to.
|
||||
|
||||
---
|
||||
|
||||
## Connecting Applications
|
||||
|
||||
### Keycloak
|
||||
|
||||
Keycloak uses LDAP for user federation. Configure it with:
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Connection URL | `ldap://openldap:389` |
|
||||
| Bind DN | `cn=keycloak,ou=Services,dc=example,dc=com` |
|
||||
| Bind Credential | (from service-passwords.txt) |
|
||||
| Users DN | `ou=People,dc=example,dc=com` |
|
||||
| Username Attribute | `uid` |
|
||||
| RDN Attribute | `uid` |
|
||||
| UUID Attribute | `entryUUID` |
|
||||
|
||||
### Nextcloud
|
||||
|
||||
In Nextcloud's LDAP settings:
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Host | `openldap` |
|
||||
| Port | `389` |
|
||||
| User DN | `cn=nextcloud,ou=Services,dc=example,dc=com` |
|
||||
| Password | (from service-passwords.txt) |
|
||||
| Base DN | `dc=example,dc=com` |
|
||||
| User Filter | `(&(objectClass=inetOrgPerson)(uid=%uid))` |
|
||||
| Group Filter | `(objectClass=groupOfMembers)` |
|
||||
|
||||
### SSSD (Linux PAM/NSS)
|
||||
|
||||
For Linux systems to authenticate against LDAP:
|
||||
|
||||
```ini
|
||||
# /etc/sssd/sssd.conf
|
||||
[sssd]
|
||||
services = nss, pam
|
||||
domains = example.com
|
||||
|
||||
[domain/example.com]
|
||||
id_provider = ldap
|
||||
auth_provider = ldap
|
||||
ldap_uri = ldap://openldap.example.com
|
||||
ldap_search_base = dc=example,dc=com
|
||||
ldap_default_bind_dn = cn=sssd,ou=Services,dc=example,dc=com
|
||||
ldap_default_authtok = sssd-password-here
|
||||
ldap_user_search_base = ou=People,dc=example,dc=com
|
||||
ldap_group_search_base = ou=Groups,dc=example,dc=com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Why Backups Matter
|
||||
|
||||
The LDAP directory contains:
|
||||
- All user accounts and credentials
|
||||
- Group memberships and access control
|
||||
- Service account configurations
|
||||
- Password policies
|
||||
|
||||
Losing this data means losing access to all integrated systems.
|
||||
|
||||
### Creating Backups
|
||||
|
||||
Export the directory to LDIF format:
|
||||
|
||||
```bash
|
||||
# Backup user data (database 1)
|
||||
docker exec openldap slapcat -n 1 > ldap-data-$(date +%Y%m%d).ldif
|
||||
|
||||
# Backup configuration (database 0) - optional but recommended
|
||||
docker exec openldap slapcat -n 0 > ldap-config-$(date +%Y%m%d).ldif
|
||||
```
|
||||
|
||||
**Backup frequency recommendation:**
|
||||
- Daily for the data backup
|
||||
- After any configuration changes for the config backup
|
||||
|
||||
### Restoring from Backup
|
||||
|
||||
**Warning**: This destroys all existing data!
|
||||
|
||||
```bash
|
||||
# Stop the container
|
||||
docker stop openldap
|
||||
docker rm openldap
|
||||
|
||||
# Remove existing volumes
|
||||
docker volume rm openldap-data openldap-config
|
||||
|
||||
# Create fresh container with backup
|
||||
docker run --rm \
|
||||
-v openldap-data:/var/lib/openldap/openldap-data \
|
||||
-v $(pwd)/ldap-data-20240101.ldif:/backup.ldif:ro \
|
||||
enterprise-openldap:latest \
|
||||
sh -c "slapadd -n 1 -l /backup.ldif && chown -R ldap:ldap /var/lib/openldap"
|
||||
|
||||
# Start normally
|
||||
docker run -d --name openldap \
|
||||
-v openldap-data:/var/lib/openldap/openldap-data \
|
||||
-v openldap-config:/etc/openldap/slapd.d \
|
||||
... # your normal settings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Understanding Log Levels
|
||||
|
||||
The `LDAP_LOG_LEVEL` controls what gets logged. Common values:
|
||||
|
||||
| Level | What it shows | When to use |
|
||||
|-------|---------------|-------------|
|
||||
| `0` | Nothing | Never in production |
|
||||
| `256` | Connections, operations, results | Normal operation |
|
||||
| `384` | Above + ACL decisions | Debugging access issues |
|
||||
| `512` | Stats with entry counts | Performance analysis |
|
||||
| `-1` | Everything | Last resort debugging |
|
||||
|
||||
Increase temporarily for debugging:
|
||||
|
||||
```bash
|
||||
docker run -e LDAP_LOG_LEVEL=384 ...
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Container exits immediately
|
||||
|
||||
**Symptom**: Container starts and stops within seconds.
|
||||
|
||||
**Cause**: Missing required environment variables.
|
||||
|
||||
**Solution**: Check that `LDAP_DOMAIN`, `LDAP_ORGANISATION`, and `LDAP_ADMIN_PASSWORD` are all set.
|
||||
|
||||
```bash
|
||||
docker logs openldap # Will show which variable is missing
|
||||
```
|
||||
|
||||
#### Cannot authenticate as admin
|
||||
|
||||
**Symptom**: `ldap_bind: Invalid credentials (49)`
|
||||
|
||||
**Causes**:
|
||||
1. Wrong password
|
||||
2. Wrong bind DN (check the domain components match your LDAP_DOMAIN)
|
||||
3. Typing `dc=example.com` instead of `dc=example,dc=com`
|
||||
|
||||
**Solution**: Verify the exact bind DN:
|
||||
|
||||
```bash
|
||||
# Your bind DN is always: cn=admin,<your-base-dn>
|
||||
# If LDAP_DOMAIN=example.com, then it's: cn=admin,dc=example,dc=com
|
||||
```
|
||||
|
||||
#### TLS connection refused
|
||||
|
||||
**Symptom**: Cannot connect to port 636.
|
||||
|
||||
**Cause**: Certificates not found or not readable.
|
||||
|
||||
**Solution**: Check the logs for TLS errors:
|
||||
|
||||
```bash
|
||||
docker logs openldap | grep -i tls
|
||||
```
|
||||
|
||||
Verify certificates are mounted correctly:
|
||||
|
||||
```bash
|
||||
docker exec openldap ls -la /certs/
|
||||
```
|
||||
|
||||
#### Service account cannot search
|
||||
|
||||
**Symptom**: Service account binds successfully but searches return empty.
|
||||
|
||||
**Cause**: ACLs restrict what the service account can see.
|
||||
|
||||
**Solution**: Service accounts can only read specific OUs. Verify you're searching the correct base:
|
||||
|
||||
```bash
|
||||
# Correct - searching People OU
|
||||
ldapsearch -D "cn=keycloak,ou=Services,..." -b "ou=People,dc=example,dc=com" ...
|
||||
|
||||
# Wrong - searching the entire tree
|
||||
ldapsearch -D "cn=keycloak,ou=Services,..." -b "dc=example,dc=com" ...
|
||||
```
|
||||
|
||||
#### Users don't have memberOf attribute
|
||||
|
||||
**Symptom**: User exists in group but `memberOf` attribute is empty.
|
||||
|
||||
**Cause**: User was added before the memberOf overlay, or group uses wrong attribute.
|
||||
|
||||
**Solution**: The memberOf overlay only tracks `member` attribute on `groupOfMembers` objects. Verify:
|
||||
|
||||
1. Group has `objectClass: groupOfMembers`
|
||||
2. Group uses `member` attribute (not `memberUid`)
|
||||
|
||||
To fix existing users, remove and re-add them to groups.
|
||||
|
||||
---
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
### Password Security
|
||||
|
||||
1. **Use strong admin password** - At least 16 characters with mixed case, numbers, symbols
|
||||
2. **Rotate service account passwords** periodically
|
||||
3. **Delete service-passwords.txt** after retrieving the passwords
|
||||
4. **Never commit passwords** to version control
|
||||
|
||||
### Network Security
|
||||
|
||||
1. **Use TLS** for all production deployments
|
||||
2. **Restrict port access** - Only allow LDAP ports from trusted networks
|
||||
3. **Use internal Docker networks** when possible:
|
||||
|
||||
```bash
|
||||
docker network create ldap-net
|
||||
docker run --network ldap-net --name openldap ...
|
||||
# Other containers on ldap-net can access via hostname "openldap"
|
||||
```
|
||||
|
||||
### Access Control
|
||||
|
||||
1. **Use service accounts** instead of sharing admin credentials
|
||||
2. **Principle of least privilege** - Service accounts only get read access
|
||||
3. **Audit regularly** - Check who has access to what
|
||||
|
||||
---
|
||||
|
||||
## Docker Compose Reference
|
||||
|
||||
Complete production-ready example:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
openldap:
|
||||
image: enterprise-openldap:latest
|
||||
container_name: openldap
|
||||
hostname: ldap.example.com
|
||||
environment:
|
||||
LDAP_DOMAIN: example.com
|
||||
LDAP_ORGANISATION: "Example Corporation"
|
||||
LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD:?Set LDAP_ADMIN_PASSWORD}
|
||||
LDAP_TLS_ENABLED: "true"
|
||||
LDAP_CREATE_SERVICE_ACCOUNTS: "true"
|
||||
LDAP_LOG_LEVEL: "256"
|
||||
volumes:
|
||||
- openldap-data:/var/lib/openldap/openldap-data
|
||||
- openldap-config:/etc/openldap/slapd.d
|
||||
- ./certs:/certs:ro
|
||||
ports:
|
||||
- "389:389"
|
||||
- "636:636"
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "ldapsearch", "-x", "-H", "ldap://localhost", "-b", "", "-s", "base"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
openldap-data:
|
||||
openldap-config:
|
||||
```
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```bash
|
||||
LDAP_ADMIN_PASSWORD=your-secure-password-here
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
31
docker-compose.yml
Normal file
31
docker-compose.yml
Normal file
@ -0,0 +1,31 @@
|
||||
services:
|
||||
openldap:
|
||||
build: .
|
||||
image: enterprise-openldap:latest
|
||||
container_name: openldap
|
||||
hostname: ldap.example.com
|
||||
environment:
|
||||
LDAP_DOMAIN: example.com
|
||||
LDAP_ORGANISATION: "Example Corporation"
|
||||
LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD:-changeme}
|
||||
LDAP_TLS_ENABLED: "true"
|
||||
LDAP_CREATE_SERVICE_ACCOUNTS: "true"
|
||||
LDAP_LOG_LEVEL: "256"
|
||||
volumes:
|
||||
- ldap_data:/var/lib/openldap/openldap-data
|
||||
- ldap_config:/etc/openldap/slapd.d
|
||||
- ./certs:/certs:ro
|
||||
- ./ldif:/ldif/custom:ro
|
||||
ports:
|
||||
- "389:389"
|
||||
- "636:636"
|
||||
networks:
|
||||
- ldap_net
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
ldap_data:
|
||||
ldap_config:
|
||||
|
||||
networks:
|
||||
ldap_net:
|
||||
115
docker-entrypoint.sh
Normal file
115
docker-entrypoint.sh
Normal file
@ -0,0 +1,115 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Source utility functions
|
||||
. /scripts/utils.sh
|
||||
|
||||
# LDAPI socket URL - must use URL-encoded path for Alpine
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
export LDAPI_SOCKET
|
||||
|
||||
# Validate required environment variables
|
||||
if [ -z "$LDAP_DOMAIN" ]; then
|
||||
log_error "LDAP_DOMAIN is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$LDAP_ORGANISATION" ]; then
|
||||
log_error "LDAP_ORGANISATION is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$LDAP_ADMIN_PASSWORD" ]; then
|
||||
log_error "LDAP_ADMIN_PASSWORD is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate base DN from domain if not provided
|
||||
if [ -z "$LDAP_BASE_DN" ]; then
|
||||
LDAP_BASE_DN=$(echo "$LDAP_DOMAIN" | sed 's/^/dc=/; s/\./,dc=/g')
|
||||
fi
|
||||
export LDAP_BASE_DN
|
||||
|
||||
# Extract DC component for base entry
|
||||
LDAP_DC=$(echo "$LDAP_DOMAIN" | cut -d'.' -f1)
|
||||
export LDAP_DC
|
||||
|
||||
# Set defaults for optional variables
|
||||
export LDAP_CONFIG_PASSWORD="${LDAP_CONFIG_PASSWORD:-$(generate_password)}"
|
||||
export LDAP_TLS_ENABLED="${LDAP_TLS_ENABLED:-true}"
|
||||
export LDAP_TLS_CERT_FILE="${LDAP_TLS_CERT_FILE:-/certs/ldap.crt}"
|
||||
export LDAP_TLS_KEY_FILE="${LDAP_TLS_KEY_FILE:-/certs/ldap.key}"
|
||||
export LDAP_TLS_CA_FILE="${LDAP_TLS_CA_FILE:-/certs/ca.crt}"
|
||||
export LDAP_TLS_VERIFY_CLIENT="${LDAP_TLS_VERIFY_CLIENT:-try}"
|
||||
export LDAP_LOG_LEVEL="${LDAP_LOG_LEVEL:-256}"
|
||||
export LDAP_READONLY="${LDAP_READONLY:-false}"
|
||||
|
||||
log_info "OpenLDAP Container Starting"
|
||||
log_info "Domain: $LDAP_DOMAIN"
|
||||
log_info "Base DN: $LDAP_BASE_DN"
|
||||
log_info "Organisation: $LDAP_ORGANISATION"
|
||||
|
||||
# Check if already initialized
|
||||
if [ ! -f /var/lib/openldap/openldap-data/data.mdb ]; then
|
||||
log_info "First run - initializing OpenLDAP..."
|
||||
|
||||
# Initialize cn=config
|
||||
/scripts/init-config.sh
|
||||
|
||||
# Load schemas in order
|
||||
/scripts/init-schemas.sh
|
||||
|
||||
# Configure overlays
|
||||
/scripts/init-overlays.sh
|
||||
|
||||
# Create base DIT
|
||||
/scripts/init-dit.sh
|
||||
|
||||
# Configure ACLs
|
||||
/scripts/init-acls.sh
|
||||
|
||||
# Create service accounts if requested
|
||||
if [ "$LDAP_CREATE_SERVICE_ACCOUNTS" = "true" ]; then
|
||||
/scripts/init-services.sh
|
||||
fi
|
||||
|
||||
# Process custom LDIF files if present
|
||||
if [ -d /ldif/custom ] && [ "$(ls -A /ldif/custom 2>/dev/null)" ]; then
|
||||
log_info "Processing custom LDIF files..."
|
||||
for ldif in /ldif/custom/*.ldif; do
|
||||
if [ -f "$ldif" ]; then
|
||||
log_info "Loading: $ldif"
|
||||
ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,$LDAP_BASE_DN" -w "$LDAP_ADMIN_PASSWORD" -f "$ldif" || \
|
||||
log_warn "Failed to load $ldif (may already exist)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log_info "Initialization complete."
|
||||
else
|
||||
log_info "Database exists - starting normally."
|
||||
fi
|
||||
|
||||
# Ensure proper ownership
|
||||
chown -R ldap:ldap /var/lib/openldap
|
||||
chown -R ldap:ldap /etc/openldap/slapd.d
|
||||
chown -R ldap:ldap /run/openldap
|
||||
|
||||
# Build slapd arguments
|
||||
SLAPD_URLS="ldap:/// $LDAPI_SOCKET"
|
||||
if [ "$LDAP_TLS_ENABLED" = "true" ]; then
|
||||
if [ -f "$LDAP_TLS_CERT_FILE" ] && [ -f "$LDAP_TLS_KEY_FILE" ]; then
|
||||
SLAPD_URLS="ldap:/// ldaps:/// $LDAPI_SOCKET"
|
||||
log_info "TLS enabled - listening on ldaps://"
|
||||
else
|
||||
log_warn "TLS enabled but certificates not found - skipping ldaps://"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Starting slapd with URLs: $SLAPD_URLS"
|
||||
|
||||
# Start slapd
|
||||
exec /usr/sbin/slapd -h "$SLAPD_URLS" \
|
||||
-F /etc/openldap/slapd.d \
|
||||
-u ldap -g ldap \
|
||||
-d "${LDAP_LOG_LEVEL}"
|
||||
2
ldif/.gitkeep
Normal file
2
ldif/.gitkeep
Normal file
@ -0,0 +1,2 @@
|
||||
# Place custom LDIF files here for automatic loading during initialization
|
||||
# Files will be loaded in alphabetical order after base DIT creation
|
||||
206
schema/enterprise.schema
Normal file
206
schema/enterprise.schema
Normal file
@ -0,0 +1,206 @@
|
||||
# ===========================================================================
|
||||
# Enterprise Platform LDAP Schema
|
||||
# Version: 1.0
|
||||
#
|
||||
# OID Base: 1.3.6.1.4.1.99999 (DEVELOPMENT - Apply for your own from IANA)
|
||||
#
|
||||
# Structure:
|
||||
# 1.3.6.1.4.1.99999.1 - Enterprise Platform
|
||||
# 1.3.6.1.4.1.99999.1.1 - Attribute Types
|
||||
# 1.3.6.1.4.1.99999.1.2 - Object Classes
|
||||
#
|
||||
# Includes:
|
||||
# - Virtual Mail (Postfix/Dovecot)
|
||||
# - Nextcloud integration
|
||||
# - Service access control
|
||||
#
|
||||
# Dependencies (must be loaded first):
|
||||
# - core.schema
|
||||
# - cosine.schema
|
||||
# - inetorgperson.schema
|
||||
# - rfc2307bis.schema
|
||||
# ===========================================================================
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SECTION 1: VIRTUAL MAIL ATTRIBUTES AND OBJECTS
|
||||
# ===========================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mail Attribute Types
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.1
|
||||
NAME 'mailDomain'
|
||||
DESC 'Virtual mail domain name'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.2
|
||||
NAME 'mailTransport'
|
||||
DESC 'Postfix transport (e.g., lmtp:unix:private/dovecot-lmtp)'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.3
|
||||
NAME 'mailbox'
|
||||
DESC 'Relative mailbox path (e.g., domain.com/user/)'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.4
|
||||
NAME 'mailQuota'
|
||||
DESC 'Mailbox quota in bytes'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.5
|
||||
NAME 'mailEnabled'
|
||||
DESC 'Mail account or domain enabled (TRUE/FALSE)'
|
||||
EQUALITY booleanMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.6
|
||||
NAME 'maildrop'
|
||||
DESC 'Final delivery address or forward destination'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.7
|
||||
NAME 'mailAlias'
|
||||
DESC 'Additional email addresses for this account'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.8
|
||||
NAME 'mailHomeDirectory'
|
||||
DESC 'Base path for mail storage'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.9
|
||||
NAME 'domainQuota'
|
||||
DESC 'Total quota for all accounts in domain (bytes)'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.10
|
||||
NAME 'domainMaxAccounts'
|
||||
DESC 'Maximum number of accounts in domain'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.99999.1.1.11
|
||||
NAME 'domainMaxAliases'
|
||||
DESC 'Maximum number of aliases in domain'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mail Object Classes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.99999.1.2.1
|
||||
NAME 'mailDomainObject'
|
||||
DESC 'Virtual mail domain'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( mailDomain $ mailEnabled )
|
||||
MAY ( mailTransport $ mailHomeDirectory $ domainQuota $
|
||||
domainMaxAccounts $ domainMaxAliases $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.99999.1.2.2
|
||||
NAME 'mailAccountObject'
|
||||
DESC 'Virtual mail account - extends inetOrgPerson'
|
||||
SUP inetOrgPerson STRUCTURAL
|
||||
MUST ( mail $ mailEnabled )
|
||||
MAY ( mailbox $ mailQuota $ maildrop $ mailAlias $
|
||||
mailHomeDirectory $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.99999.1.2.3
|
||||
NAME 'mailAliasObject'
|
||||
DESC 'Mail alias or distribution list'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( mail $ maildrop $ mailEnabled )
|
||||
MAY ( cn $ description ) )
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SECTION 2: NEXTCLOUD ATTRIBUTES AND OBJECTS
|
||||
# Using official Nextcloud OIDs (1.3.6.1.4.1.49213.1) for compatibility
|
||||
# ===========================================================================
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.49213.1.1.1
|
||||
NAME 'nextcloudEnabled'
|
||||
DESC 'Whether user or group should be available in Nextcloud'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SUBSTR caseIgnoreSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.49213.1.1.2
|
||||
NAME 'nextcloudQuota'
|
||||
DESC 'Nextcloud disk quota (e.g., 15 GB)'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SUBSTR caseIgnoreSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.49213.1.2.1
|
||||
NAME 'nextcloudUser'
|
||||
DESC 'Nextcloud user account'
|
||||
SUP top AUXILIARY
|
||||
MAY ( nextcloudEnabled $ nextcloudQuota ) )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.49213.1.2.2
|
||||
NAME 'nextcloudGroup'
|
||||
DESC 'Nextcloud group'
|
||||
SUP top AUXILIARY
|
||||
MAY ( nextcloudEnabled ) )
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SECTION 3: SERVICE ACCESS CONTROL
|
||||
# Based on PADL ldapns schema (OID 1.3.6.1.4.1.5765)
|
||||
# ===========================================================================
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.5765.100.1
|
||||
NAME 'authorizedService'
|
||||
DESC 'Service authorized for this account'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SUBSTR caseIgnoreSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.5765.100.2
|
||||
NAME 'authorizedServiceObject'
|
||||
DESC 'Service authorization object'
|
||||
SUP top AUXILIARY
|
||||
MAY ( authorizedService ) )
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SECTION 4: ADDITIONAL UTILITY OBJECTS
|
||||
# ===========================================================================
|
||||
|
||||
# Service account object for bind DNs
|
||||
objectclass ( 1.3.6.1.4.1.99999.1.2.10
|
||||
NAME 'serviceAccount'
|
||||
DESC 'Service account for application binding'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( cn )
|
||||
MAY ( description $ userPassword ) )
|
||||
291
schema/kerberos.schema
Normal file
291
schema/kerberos.schema
Normal file
@ -0,0 +1,291 @@
|
||||
# ===========================================================================
|
||||
# MIT Kerberos LDAP Schema
|
||||
#
|
||||
# This schema enables storing Kerberos principals in OpenLDAP.
|
||||
# It is loaded by default but remains DORMANT until Kerberos is enabled.
|
||||
#
|
||||
# OID Base: 2.16.840.1.113719.1.301
|
||||
#
|
||||
# When Kerberos is enabled:
|
||||
# 1. krbPrincipalAux objectClass is added to user entries
|
||||
# 2. MIT KDC is deployed with LDAP backend
|
||||
# 3. Principals are created via kadmin
|
||||
#
|
||||
# Source: MIT Kerberos source code (src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema)
|
||||
# Reference: https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_ldap.html
|
||||
#
|
||||
# Dependencies:
|
||||
# - core.schema
|
||||
# ===========================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Attribute Types
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.1.1
|
||||
NAME 'krbPrincipalName'
|
||||
DESC 'Kerberos principal name (e.g., user@REALM)'
|
||||
EQUALITY caseExactIA5Match
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.2.1
|
||||
NAME 'krbPrincipalKey'
|
||||
DESC 'Kerberos principal key data (managed by KDC)'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.3.1
|
||||
NAME 'krbTicketPolicyReference'
|
||||
DESC 'DN of ticket policy'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.4.1
|
||||
NAME 'krbPrincipalExpiration'
|
||||
DESC 'Principal expiration time'
|
||||
EQUALITY generalizedTimeMatch
|
||||
ORDERING generalizedTimeOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.5.1
|
||||
NAME 'krbPasswordExpiration'
|
||||
DESC 'Password expiration time'
|
||||
EQUALITY generalizedTimeMatch
|
||||
ORDERING generalizedTimeOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.6.1
|
||||
NAME 'krbMaxTicketLife'
|
||||
DESC 'Maximum ticket lifetime in seconds'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.7.1
|
||||
NAME 'krbMaxRenewableLife'
|
||||
DESC 'Maximum renewable ticket lifetime in seconds'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.8.1
|
||||
NAME 'krbTicketFlags'
|
||||
DESC 'Kerberos ticket flags'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.9.1
|
||||
NAME 'krbPrincipalType'
|
||||
DESC 'Kerberos principal type'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.10.1
|
||||
NAME 'krbPwdPolicyReference'
|
||||
DESC 'DN of password policy'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.11.1
|
||||
NAME 'krbPrincipalReferences'
|
||||
DESC 'DN of associated principal entries'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.12.1
|
||||
NAME 'krbLastPwdChange'
|
||||
DESC 'Time of last password change'
|
||||
EQUALITY generalizedTimeMatch
|
||||
ORDERING generalizedTimeOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.13.1
|
||||
NAME 'krbLastSuccessfulAuth'
|
||||
DESC 'Time of last successful authentication'
|
||||
EQUALITY generalizedTimeMatch
|
||||
ORDERING generalizedTimeOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.14.1
|
||||
NAME 'krbLastFailedAuth'
|
||||
DESC 'Time of last failed authentication'
|
||||
EQUALITY generalizedTimeMatch
|
||||
ORDERING generalizedTimeOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.15.1
|
||||
NAME 'krbLoginFailedCount'
|
||||
DESC 'Number of consecutive failed login attempts'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.16.1
|
||||
NAME 'krbExtraData'
|
||||
DESC 'Extra data for Kerberos'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.17.1
|
||||
NAME 'krbAllowedToDelegateTo'
|
||||
DESC 'Services this principal can delegate to (S4U2Proxy)'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Realm Container Attributes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.20.1
|
||||
NAME 'krbSubTrees'
|
||||
DESC 'DNs of subtrees containing principals'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.21.1
|
||||
NAME 'krbSearchScope'
|
||||
DESC 'Search scope for principals (0=base, 1=one, 2=sub)'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.22.1
|
||||
NAME 'krbPrincContainerRef'
|
||||
DESC 'DN of principal container'
|
||||
EQUALITY distinguishedNameMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.23.1
|
||||
NAME 'krbMaxPwdLife'
|
||||
DESC 'Maximum password lifetime in realm'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.24.1
|
||||
NAME 'krbMinPwdLife'
|
||||
DESC 'Minimum password lifetime in realm'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.25.1
|
||||
NAME 'krbPwdMinDiffChars'
|
||||
DESC 'Minimum number of character classes in password'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.26.1
|
||||
NAME 'krbPwdMinLength'
|
||||
DESC 'Minimum password length'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.27.1
|
||||
NAME 'krbPwdHistoryLength'
|
||||
DESC 'Number of passwords to keep in history'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.28.1
|
||||
NAME 'krbPwdMaxFailure'
|
||||
DESC 'Maximum password failures before lockout'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.29.1
|
||||
NAME 'krbPwdFailureCountInterval'
|
||||
DESC 'Failure count reset interval in seconds'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 2.16.840.1.113719.1.301.4.30.1
|
||||
NAME 'krbPwdLockoutDuration'
|
||||
DESC 'Lockout duration in seconds'
|
||||
EQUALITY integerMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Object Classes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Auxiliary class for adding Kerberos attributes to user entries
|
||||
# This is what gets added to users when Kerberos premium feature is enabled
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.8.1
|
||||
NAME 'krbPrincipalAux'
|
||||
DESC 'Auxiliary class for Kerberos principal attributes'
|
||||
SUP top AUXILIARY
|
||||
MAY ( krbPrincipalName $ krbPrincipalKey $ krbTicketPolicyReference $
|
||||
krbPrincipalExpiration $ krbPasswordExpiration $
|
||||
krbMaxTicketLife $ krbMaxRenewableLife $ krbTicketFlags $
|
||||
krbPrincipalType $ krbPwdPolicyReference $ krbPrincipalReferences $
|
||||
krbLastPwdChange $ krbLastSuccessfulAuth $ krbLastFailedAuth $
|
||||
krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
|
||||
|
||||
# Structural class for standalone principal entries (less common)
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.9.1
|
||||
NAME 'krbPrincipal'
|
||||
DESC 'Structural class for Kerberos principals'
|
||||
SUP top STRUCTURAL
|
||||
MUST krbPrincipalName
|
||||
MAY ( krbPrincipalKey $ krbTicketPolicyReference $
|
||||
krbPrincipalExpiration $ krbPasswordExpiration $
|
||||
krbMaxTicketLife $ krbMaxRenewableLife $ krbTicketFlags $
|
||||
krbPrincipalType $ krbPwdPolicyReference $ krbPrincipalReferences $
|
||||
krbLastPwdChange $ krbLastSuccessfulAuth $ krbLastFailedAuth $
|
||||
krbLoginFailedCount $ krbExtraData $ krbAllowedToDelegateTo ) )
|
||||
|
||||
# Container for Kerberos realm
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.1.1
|
||||
NAME 'krbRealmContainer'
|
||||
DESC 'Container for Kerberos realm'
|
||||
SUP top STRUCTURAL
|
||||
MUST cn
|
||||
MAY ( krbSubTrees $ krbSearchScope $ krbPrincContainerRef $
|
||||
krbMaxTicketLife $ krbMaxRenewableLife $ krbTicketFlags ) )
|
||||
|
||||
# Ticket policy object
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.2.1
|
||||
NAME 'krbTicketPolicy'
|
||||
DESC 'Kerberos ticket policy'
|
||||
SUP top STRUCTURAL
|
||||
MUST cn
|
||||
MAY ( krbMaxTicketLife $ krbMaxRenewableLife $ krbTicketFlags ) )
|
||||
|
||||
# Password policy object for Kerberos
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.3.1
|
||||
NAME 'krbPwdPolicy'
|
||||
DESC 'Kerberos password policy'
|
||||
SUP top STRUCTURAL
|
||||
MUST cn
|
||||
MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $
|
||||
krbPwdMinLength $ krbPwdHistoryLength $ krbPwdMaxFailure $
|
||||
krbPwdFailureCountInterval $ krbPwdLockoutDuration ) )
|
||||
|
||||
# Service principal container
|
||||
objectclass ( 2.16.840.1.113719.1.301.6.4.1
|
||||
NAME 'krbService'
|
||||
DESC 'Kerberos service'
|
||||
SUP krbPrincipal STRUCTURAL )
|
||||
29
schema/openssh-lpk.schema
Normal file
29
schema/openssh-lpk.schema
Normal file
@ -0,0 +1,29 @@
|
||||
#
|
||||
# OpenSSH LDAP Public Key Schema
|
||||
# Used by: SSH servers, Gitea, GitLab CE (with LDAP sync)
|
||||
#
|
||||
# OID: 1.3.6.1.4.1.24552.500.1.1 (OpenSSH project registered OID)
|
||||
#
|
||||
# Installation:
|
||||
# Include in slapd.conf or convert to LDIF for cn=config
|
||||
#
|
||||
# Usage:
|
||||
# Add 'ldapPublicKey' objectClass to user entries
|
||||
# Add 'sshPublicKey' attribute with public key(s)
|
||||
#
|
||||
# SSH Server Config (/etc/ssh/sshd_config):
|
||||
# AuthorizedKeysCommand /usr/local/bin/ldap-ssh-keys.sh
|
||||
# AuthorizedKeysCommandUser nobody
|
||||
#
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13
|
||||
NAME 'sshPublicKey'
|
||||
DESC 'OpenSSH Public Key'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0
|
||||
NAME 'ldapPublicKey'
|
||||
DESC 'OpenSSH LDAP Public Key Objectclass'
|
||||
SUP top AUXILIARY
|
||||
MAY ( sshPublicKey $ uid ) )
|
||||
340
schema/rfc2307bis.schema
Normal file
340
schema/rfc2307bis.schema
Normal file
@ -0,0 +1,340 @@
|
||||
# ===========================================================================
|
||||
# RFC2307bis Schema
|
||||
#
|
||||
# This schema provides POSIX account and group support with the critical
|
||||
# difference from RFC2307 (nis.schema) that posixGroup is AUXILIARY,
|
||||
# allowing it to be combined with groupOfNames/groupOfMembers for
|
||||
# proper memberOf overlay support.
|
||||
#
|
||||
# Source: https://github.com/jtyr/rfc2307bis
|
||||
# See also: https://tools.ietf.org/html/draft-howard-rfc2307bis-02
|
||||
#
|
||||
# Key differences from nis.schema (RFC2307):
|
||||
# - posixGroup is AUXILIARY (not STRUCTURAL)
|
||||
# - Allows combining with groupOfNames/groupOfMembers
|
||||
# - memberOf overlay works correctly
|
||||
# - Empty groups are allowed
|
||||
#
|
||||
# Dependencies:
|
||||
# - core.schema
|
||||
# - cosine.schema
|
||||
#
|
||||
# DO NOT load nis.schema alongside this schema - they conflict!
|
||||
#
|
||||
# NOTE: On Alpine Linux, uidNumber and gidNumber are built-in to OpenLDAP
|
||||
# and must NOT be redefined. They are commented out below.
|
||||
# ===========================================================================
|
||||
|
||||
# Attribute types from RFC 2307
|
||||
|
||||
# uidNumber is built-in on Alpine Linux OpenLDAP
|
||||
#attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber'
|
||||
# DESC 'An integer uniquely identifying a user in an administrative domain'
|
||||
# EQUALITY integerMatch
|
||||
# ORDERING integerOrderingMatch
|
||||
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
# SINGLE-VALUE )
|
||||
|
||||
# gidNumber is built-in on Alpine Linux OpenLDAP
|
||||
#attributetype ( 1.3.6.1.1.1.1.1 NAME 'gidNumber'
|
||||
# DESC 'An integer uniquely identifying a group in an administrative domain'
|
||||
# EQUALITY integerMatch
|
||||
# ORDERING integerOrderingMatch
|
||||
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
# SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
|
||||
DESC 'The GECOS field; the common name'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SUBSTR caseIgnoreSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory'
|
||||
DESC 'The absolute path to the home directory'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.4 NAME 'loginShell'
|
||||
DESC 'The path to the login shell'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.6 NAME 'shadowMin'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.7 NAME 'shadowMax'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.12 NAME 'memberUid'
|
||||
DESC 'Member UID - for backwards compatibility with RFC2307'
|
||||
EQUALITY caseExactMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup'
|
||||
EQUALITY caseExactMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple'
|
||||
DESC 'Netgroup triple'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SUBSTR caseIgnoreSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort'
|
||||
DESC 'Service port number'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol'
|
||||
DESC 'Service protocol name'
|
||||
SUP name )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber'
|
||||
DESC 'IP protocol number'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber'
|
||||
DESC 'ONC RPC number'
|
||||
EQUALITY integerMatch
|
||||
ORDERING integerOrderingMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber'
|
||||
DESC 'IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC 4291'
|
||||
SUP name )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber'
|
||||
DESC 'IP network omitting leading zeros, eg. 192.168'
|
||||
SUP name
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber'
|
||||
DESC 'IP netmask omitting leading zeros, eg. 255.255.255.0'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.22 NAME 'macAddress'
|
||||
DESC 'MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.23 NAME 'bootParameter'
|
||||
DESC 'rpc.bootparamd parameter'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.24 NAME 'bootFile'
|
||||
DESC 'Boot image name'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName'
|
||||
DESC 'Name of a generic NIS map'
|
||||
SUP name )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry'
|
||||
DESC 'A generic NIS entry'
|
||||
EQUALITY caseExactMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.28 NAME 'nisPublicKey'
|
||||
DESC 'NIS public key'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.29 NAME 'nisSecretKey'
|
||||
DESC 'NIS secret key'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.30 NAME 'nisDomain'
|
||||
DESC 'NIS domain'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.31 NAME 'automountMapName'
|
||||
DESC 'automount Map Name'
|
||||
EQUALITY caseExactMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.32 NAME 'automountKey'
|
||||
DESC 'Automount Key value'
|
||||
EQUALITY caseExactMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.1.1.1.33 NAME 'automountInformation'
|
||||
DESC 'Automount information'
|
||||
EQUALITY caseExactMatch
|
||||
SUBSTR caseExactSubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SINGLE-VALUE )
|
||||
|
||||
# Object classes from RFC 2307bis
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
|
||||
DESC 'Abstraction of an account with POSIX attributes'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
|
||||
MAY ( userPassword $ loginShell $ gecos $ description ) )
|
||||
|
||||
# THIS IS THE KEY DIFFERENCE FROM RFC2307:
|
||||
# posixGroup is AUXILIARY, not STRUCTURAL
|
||||
# This allows combining with groupOfNames/groupOfMembers
|
||||
objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
|
||||
DESC 'Abstraction of a group of accounts'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ gidNumber )
|
||||
MAY ( userPassword $ memberUid $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount'
|
||||
DESC 'Additional attributes for shadow passwords'
|
||||
SUP top AUXILIARY
|
||||
MUST uid
|
||||
MAY ( userPassword $ description $
|
||||
shadowLastChange $ shadowMin $ shadowMax $
|
||||
shadowWarning $ shadowInactive $
|
||||
shadowExpire $ shadowFlag ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.3 NAME 'ipService'
|
||||
DESC 'Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( cn $ ipServicePort $ ipServiceProtocol )
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol'
|
||||
DESC 'Abstraction of an IP protocol. Maps a protocol number to one or more names'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( cn $ ipProtocolNumber )
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.5 NAME 'oncRpc'
|
||||
DESC 'Abstraction of an Open Network Computing (ONC) Remote Procedure Call (RPC) binding'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( cn $ oncRpcNumber )
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.6 NAME 'ipHost'
|
||||
DESC 'Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the hosts canonical name'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ ipHostNumber )
|
||||
MAY ( userPassword $ l $ description $ manager ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork'
|
||||
DESC 'Abstraction of a network. The distinguished value of the cn attribute denotes the networks canonical name'
|
||||
SUP top STRUCTURAL
|
||||
MUST ipNetworkNumber
|
||||
MAY ( cn $ ipNetmaskNumber $ l $ description $ manager ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup'
|
||||
DESC 'Abstraction of a netgroup. May refer to other netgroups'
|
||||
SUP top STRUCTURAL
|
||||
MUST cn
|
||||
MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.9 NAME 'nisMap'
|
||||
DESC 'A generic abstraction of a NIS map'
|
||||
SUP top STRUCTURAL
|
||||
MUST nisMapName
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.10 NAME 'nisObject'
|
||||
DESC 'An entry in a NIS map'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( cn $ nisMapEntry $ nisMapName )
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device'
|
||||
DESC 'A device with a MAC address'
|
||||
SUP top AUXILIARY
|
||||
MAY macAddress )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice'
|
||||
DESC 'A device with boot parameters'
|
||||
SUP top AUXILIARY
|
||||
MAY ( bootFile $ bootParameter ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject'
|
||||
DESC 'An object with a public and secret key'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ nisPublicKey $ nisSecretKey )
|
||||
MAY ( uidNumber $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.15 NAME 'nisDomainObject'
|
||||
DESC 'Associates a NIS domain with a naming context'
|
||||
SUP top AUXILIARY
|
||||
MUST nisDomain )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.16 NAME 'automountMap'
|
||||
DESC 'An group of related automount objects'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( automountMapName )
|
||||
MAY description )
|
||||
|
||||
objectclass ( 1.3.6.1.1.1.2.17 NAME 'automount'
|
||||
DESC 'An automount entry'
|
||||
SUP top STRUCTURAL
|
||||
MUST ( automountKey $ automountInformation )
|
||||
MAY description )
|
||||
|
||||
# groupOfMembers - like groupOfNames but allows empty groups
|
||||
# This is essential for memberOf overlay support
|
||||
objectclass ( 2.16.840.1.113730.3.2.33 NAME 'groupOfMembers'
|
||||
DESC 'A group with members (like groupOfNames but member is optional)'
|
||||
SUP top STRUCTURAL
|
||||
MUST cn
|
||||
MAY ( member $ businessCategory $ description $ o $ ou $ owner $ seeAlso ) )
|
||||
44
scripts/init-acls.sh
Normal file
44
scripts/init-acls.sh
Normal file
@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Configuring ACLs..."
|
||||
|
||||
# Socket URL for ldapi - must use URL-encoded path
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
|
||||
# Start slapd temporarily
|
||||
log_info "Starting slapd temporarily for ACL configuration..."
|
||||
/usr/sbin/slapd -h "$LDAPI_SOCKET" -F /etc/openldap/slapd.d -u ldap -g ldap
|
||||
sleep 2
|
||||
|
||||
# Wait for slapd
|
||||
wait_for_slapd 30 "$LDAPI_SOCKET"
|
||||
|
||||
# Configure ACLs
|
||||
log_info "Applying ACL rules..."
|
||||
cat > /tmp/acls.ldif << EOF
|
||||
dn: olcDatabase={1}mdb,cn=config
|
||||
changetype: modify
|
||||
replace: olcAccess
|
||||
olcAccess: {0}to * by dn.exact="cn=admin,${LDAP_BASE_DN}" manage by * break
|
||||
olcAccess: {1}to attrs=userPassword by self write by anonymous auth by * none
|
||||
olcAccess: {2}to dn.children="ou=People,${LDAP_BASE_DN}" by self read by * break
|
||||
olcAccess: {3}to dn.subtree="ou=People,${LDAP_BASE_DN}" by dn.exact="cn=keycloak,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=nextcloud,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=gitea,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=sssd,ou=Services,${LDAP_BASE_DN}" read by * break
|
||||
olcAccess: {4}to dn.subtree="ou=Groups,${LDAP_BASE_DN}" by dn.exact="cn=keycloak,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=nextcloud,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=gitea,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=sssd,ou=Services,${LDAP_BASE_DN}" read by * break
|
||||
olcAccess: {5}to dn.subtree="ou=Domains,${LDAP_BASE_DN}" by dn.exact="cn=postfix,ou=Services,${LDAP_BASE_DN}" read by dn.exact="cn=dovecot,ou=Services,${LDAP_BASE_DN}" read by * break
|
||||
olcAccess: {6}to * by users read by * none
|
||||
EOF
|
||||
|
||||
ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/acls.ldif
|
||||
|
||||
# Stop temporary slapd
|
||||
log_info "Stopping temporary slapd..."
|
||||
pkill slapd || true
|
||||
sleep 2
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/acls.ldif
|
||||
|
||||
log_info "ACL configuration complete"
|
||||
107
scripts/init-config.sh
Normal file
107
scripts/init-config.sh
Normal file
@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Initializing cn=config..."
|
||||
|
||||
# Generate password hashes
|
||||
LDAP_ADMIN_PASSWORD_HASH=$(hash_password "$LDAP_ADMIN_PASSWORD")
|
||||
LDAP_CONFIG_PASSWORD_HASH=$(hash_password "$LDAP_CONFIG_PASSWORD")
|
||||
export LDAP_ADMIN_PASSWORD_HASH LDAP_CONFIG_PASSWORD_HASH
|
||||
|
||||
# Create initial slapd.d configuration
|
||||
rm -rf /etc/openldap/slapd.d/*
|
||||
|
||||
# Create base cn=config LDIF
|
||||
cat > /tmp/init-config.ldif << EOF
|
||||
dn: cn=config
|
||||
objectClass: olcGlobal
|
||||
cn: config
|
||||
olcArgsFile: /run/openldap/slapd.args
|
||||
olcPidFile: /run/openldap/slapd.pid
|
||||
olcLogLevel: ${LDAP_LOG_LEVEL}
|
||||
|
||||
dn: cn=module{0},cn=config
|
||||
objectClass: olcModuleList
|
||||
cn: module{0}
|
||||
olcModulePath: /usr/lib/openldap
|
||||
olcModuleLoad: back_mdb.so
|
||||
olcModuleLoad: memberof.so
|
||||
olcModuleLoad: refint.so
|
||||
olcModuleLoad: unique.so
|
||||
olcModuleLoad: ppolicy.so
|
||||
|
||||
dn: cn=schema,cn=config
|
||||
objectClass: olcSchemaConfig
|
||||
cn: schema
|
||||
|
||||
dn: olcDatabase={-1}frontend,cn=config
|
||||
objectClass: olcDatabaseConfig
|
||||
objectClass: olcFrontendConfig
|
||||
olcDatabase: {-1}frontend
|
||||
olcSizeLimit: 500
|
||||
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
|
||||
olcAccess: {1}to dn.base="" by * read
|
||||
olcAccess: {2}to dn.base="cn=subschema" by * read
|
||||
|
||||
dn: olcDatabase={0}config,cn=config
|
||||
objectClass: olcDatabaseConfig
|
||||
olcDatabase: {0}config
|
||||
olcRootDN: cn=admin,cn=config
|
||||
olcRootPW: ${LDAP_CONFIG_PASSWORD_HASH}
|
||||
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
|
||||
|
||||
dn: olcDatabase={1}mdb,cn=config
|
||||
objectClass: olcDatabaseConfig
|
||||
objectClass: olcMdbConfig
|
||||
olcDatabase: {1}mdb
|
||||
olcSuffix: ${LDAP_BASE_DN}
|
||||
olcRootDN: cn=admin,${LDAP_BASE_DN}
|
||||
olcRootPW: ${LDAP_ADMIN_PASSWORD_HASH}
|
||||
olcDbDirectory: /var/lib/openldap/openldap-data
|
||||
olcDbIndex: objectClass eq
|
||||
olcDbIndex: cn eq,pres,sub
|
||||
olcDbIndex: entryCSN eq
|
||||
olcDbIndex: entryUUID eq
|
||||
olcDbMaxSize: 1073741824
|
||||
EOF
|
||||
|
||||
# Add TLS configuration if enabled and certs exist
|
||||
if [ "$LDAP_TLS_ENABLED" = "true" ] && [ -f "$LDAP_TLS_CERT_FILE" ] && [ -f "$LDAP_TLS_KEY_FILE" ]; then
|
||||
log_info "Adding TLS configuration..."
|
||||
cat >> /tmp/init-config.ldif << EOF
|
||||
|
||||
dn: cn=config
|
||||
changetype: modify
|
||||
add: olcTLSCertificateFile
|
||||
olcTLSCertificateFile: ${LDAP_TLS_CERT_FILE}
|
||||
-
|
||||
add: olcTLSCertificateKeyFile
|
||||
olcTLSCertificateKeyFile: ${LDAP_TLS_KEY_FILE}
|
||||
EOF
|
||||
|
||||
if [ -f "$LDAP_TLS_CA_FILE" ]; then
|
||||
cat >> /tmp/init-config.ldif << EOF
|
||||
-
|
||||
add: olcTLSCACertificateFile
|
||||
olcTLSCACertificateFile: ${LDAP_TLS_CA_FILE}
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat >> /tmp/init-config.ldif << EOF
|
||||
-
|
||||
add: olcTLSVerifyClient
|
||||
olcTLSVerifyClient: ${LDAP_TLS_VERIFY_CLIENT}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Import the configuration
|
||||
log_info "Importing cn=config with slapadd..."
|
||||
/usr/sbin/slapadd -n 0 -F /etc/openldap/slapd.d -l /tmp/init-config.ldif
|
||||
|
||||
# Set proper ownership
|
||||
chown -R ldap:ldap /etc/openldap/slapd.d
|
||||
chown -R ldap:ldap /var/lib/openldap
|
||||
|
||||
log_info "cn=config initialization complete"
|
||||
118
scripts/init-dit.sh
Normal file
118
scripts/init-dit.sh
Normal file
@ -0,0 +1,118 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Creating base DIT structure..."
|
||||
|
||||
# Socket URL for ldapi - must use URL-encoded path
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
|
||||
# Start slapd temporarily
|
||||
log_info "Starting slapd temporarily for DIT creation..."
|
||||
/usr/sbin/slapd -h "$LDAPI_SOCKET" -F /etc/openldap/slapd.d -u ldap -g ldap
|
||||
sleep 2
|
||||
|
||||
# Wait for slapd
|
||||
wait_for_slapd 30 "$LDAPI_SOCKET"
|
||||
|
||||
# Create base DIT LDIF
|
||||
cat > /tmp/base-dit.ldif << EOF
|
||||
# Base entry
|
||||
dn: ${LDAP_BASE_DN}
|
||||
objectClass: top
|
||||
objectClass: dcObject
|
||||
objectClass: organization
|
||||
dc: ${LDAP_DC}
|
||||
o: ${LDAP_ORGANISATION}
|
||||
|
||||
# People OU
|
||||
dn: ou=People,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: People
|
||||
description: User accounts
|
||||
|
||||
# Groups OU
|
||||
dn: ou=Groups,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: Groups
|
||||
description: Authorization groups
|
||||
|
||||
# Services OU
|
||||
dn: ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: Services
|
||||
description: Service accounts for application binding
|
||||
|
||||
# Domains OU (for virtual mail domains)
|
||||
dn: ou=Domains,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: Domains
|
||||
description: Virtual mail domains
|
||||
|
||||
# Policies OU
|
||||
dn: ou=Policies,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: Policies
|
||||
description: Password and access policies
|
||||
|
||||
# Kerberos OU (for future use)
|
||||
dn: ou=Kerberos,${LDAP_BASE_DN}
|
||||
objectClass: organizationalUnit
|
||||
ou: Kerberos
|
||||
description: Kerberos realm container (premium feature)
|
||||
EOF
|
||||
|
||||
# Add base DIT
|
||||
log_info "Adding base organizational units..."
|
||||
ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,${LDAP_BASE_DN}" -w "${LDAP_ADMIN_PASSWORD}" -f /tmp/base-dit.ldif
|
||||
|
||||
# Create default password policy
|
||||
log_info "Creating default password policy..."
|
||||
cat > /tmp/default-policy.ldif << EOF
|
||||
dn: cn=default,ou=Policies,${LDAP_BASE_DN}
|
||||
objectClass: pwdPolicy
|
||||
objectClass: device
|
||||
cn: default
|
||||
pwdAttribute: userPassword
|
||||
pwdMaxAge: 7776000
|
||||
pwdExpireWarning: 1209600
|
||||
pwdInHistory: 5
|
||||
pwdCheckQuality: 2
|
||||
pwdMinLength: 12
|
||||
pwdMaxFailure: 5
|
||||
pwdLockout: TRUE
|
||||
pwdLockoutDuration: 900
|
||||
pwdGraceAuthNLimit: 3
|
||||
pwdFailureCountInterval: 900
|
||||
pwdMustChange: FALSE
|
||||
pwdAllowUserChange: TRUE
|
||||
pwdSafeModify: FALSE
|
||||
EOF
|
||||
|
||||
ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,${LDAP_BASE_DN}" -w "${LDAP_ADMIN_PASSWORD}" -f /tmp/default-policy.ldif || \
|
||||
log_warn "Password policy may already exist"
|
||||
|
||||
# Create default admin group
|
||||
log_info "Creating default admin group..."
|
||||
cat > /tmp/admin-group.ldif << EOF
|
||||
dn: cn=admins,ou=Groups,${LDAP_BASE_DN}
|
||||
objectClass: groupOfMembers
|
||||
objectClass: posixGroup
|
||||
cn: admins
|
||||
gidNumber: 10000
|
||||
description: LDAP Administrators
|
||||
EOF
|
||||
|
||||
ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,${LDAP_BASE_DN}" -w "${LDAP_ADMIN_PASSWORD}" -f /tmp/admin-group.ldif || \
|
||||
log_warn "Admin group may already exist"
|
||||
|
||||
# Stop temporary slapd
|
||||
log_info "Stopping temporary slapd..."
|
||||
pkill slapd || true
|
||||
sleep 2
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/base-dit.ldif /tmp/default-policy.ldif /tmp/admin-group.ldif
|
||||
|
||||
log_info "Base DIT creation complete"
|
||||
88
scripts/init-overlays.sh
Normal file
88
scripts/init-overlays.sh
Normal file
@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Configuring overlays..."
|
||||
|
||||
# Socket URL for ldapi - must use URL-encoded path
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
|
||||
# Start slapd temporarily to add overlays via LDAP
|
||||
log_info "Starting slapd temporarily for overlay configuration..."
|
||||
/usr/sbin/slapd -h "$LDAPI_SOCKET" -F /etc/openldap/slapd.d -u ldap -g ldap
|
||||
sleep 2
|
||||
|
||||
# Wait for slapd
|
||||
wait_for_slapd 30 "$LDAPI_SOCKET"
|
||||
|
||||
# 1. memberof overlay
|
||||
log_info "Configuring memberof overlay..."
|
||||
cat > /tmp/overlay-memberof.ldif << EOF
|
||||
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcMemberOf
|
||||
olcOverlay: memberof
|
||||
olcMemberOfRefInt: TRUE
|
||||
olcMemberOfGroupOC: groupOfMembers
|
||||
olcMemberOfMemberAD: member
|
||||
olcMemberOfMemberOfAD: memberOf
|
||||
EOF
|
||||
|
||||
ldapadd -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/overlay-memberof.ldif 2>/dev/null || \
|
||||
log_warn "memberof overlay may already exist"
|
||||
|
||||
# 2. refint (Referential Integrity) overlay
|
||||
log_info "Configuring refint overlay..."
|
||||
cat > /tmp/overlay-refint.ldif << EOF
|
||||
dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcRefintConfig
|
||||
olcOverlay: refint
|
||||
olcRefintAttribute: member
|
||||
olcRefintAttribute: memberOf
|
||||
EOF
|
||||
|
||||
ldapadd -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/overlay-refint.ldif 2>/dev/null || \
|
||||
log_warn "refint overlay may already exist"
|
||||
|
||||
# 3. unique (Attribute Uniqueness) overlay
|
||||
log_info "Configuring unique overlay..."
|
||||
cat > /tmp/overlay-unique.ldif << EOF
|
||||
dn: olcOverlay=unique,olcDatabase={1}mdb,cn=config
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcUniqueConfig
|
||||
olcOverlay: unique
|
||||
olcUniqueURI: ldap:///ou=People,${LDAP_BASE_DN}?uid?sub
|
||||
olcUniqueURI: ldap:///ou=People,${LDAP_BASE_DN}?mail?sub
|
||||
olcUniqueURI: ldap:///ou=People,${LDAP_BASE_DN}?uidNumber?sub
|
||||
olcUniqueURI: ldap:///ou=Groups,${LDAP_BASE_DN}?gidNumber?sub
|
||||
EOF
|
||||
|
||||
ldapadd -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/overlay-unique.ldif 2>/dev/null || \
|
||||
log_warn "unique overlay may already exist"
|
||||
|
||||
# 4. ppolicy (Password Policy) overlay
|
||||
log_info "Configuring ppolicy overlay..."
|
||||
cat > /tmp/overlay-ppolicy.ldif << EOF
|
||||
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcPPolicyConfig
|
||||
olcOverlay: ppolicy
|
||||
olcPPolicyDefault: cn=default,ou=Policies,${LDAP_BASE_DN}
|
||||
olcPPolicyHashCleartext: TRUE
|
||||
olcPPolicyUseLockout: TRUE
|
||||
EOF
|
||||
|
||||
ldapadd -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/overlay-ppolicy.ldif 2>/dev/null || \
|
||||
log_warn "ppolicy overlay may already exist"
|
||||
|
||||
# Stop the temporary slapd
|
||||
log_info "Stopping temporary slapd..."
|
||||
pkill slapd || true
|
||||
sleep 2
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/overlay-*.ldif
|
||||
|
||||
log_info "Overlay configuration complete"
|
||||
116
scripts/init-schemas.sh
Normal file
116
scripts/init-schemas.sh
Normal file
@ -0,0 +1,116 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Loading schemas..."
|
||||
|
||||
SCHEMA_DIR="/etc/openldap/schema"
|
||||
|
||||
# We use slaptest to convert all schemas at once into the cn=config format
|
||||
# This is more reliable than trying to load individual schemas
|
||||
|
||||
log_info "Converting schemas using slaptest..."
|
||||
|
||||
# Create a temporary slapd.conf with all schemas
|
||||
TEMP_DIR="/tmp/schema-convert-$$"
|
||||
mkdir -p "$TEMP_DIR/slapd.d"
|
||||
|
||||
cat > "$TEMP_DIR/slapd.conf" << EOF
|
||||
# Core schemas (built-in)
|
||||
include ${SCHEMA_DIR}/core.schema
|
||||
include ${SCHEMA_DIR}/cosine.schema
|
||||
include ${SCHEMA_DIR}/inetorgperson.schema
|
||||
|
||||
# Custom schemas - rfc2307bis replaces nis.schema
|
||||
include ${SCHEMA_DIR}/rfc2307bis.schema
|
||||
EOF
|
||||
|
||||
# Add openssh-lpk if it exists
|
||||
if [ -f "${SCHEMA_DIR}/openssh-lpk.schema" ]; then
|
||||
echo "include ${SCHEMA_DIR}/openssh-lpk.schema" >> "$TEMP_DIR/slapd.conf"
|
||||
fi
|
||||
|
||||
# Add kerberos if it exists
|
||||
if [ -f "${SCHEMA_DIR}/kerberos.schema" ]; then
|
||||
echo "include ${SCHEMA_DIR}/kerberos.schema" >> "$TEMP_DIR/slapd.conf"
|
||||
fi
|
||||
|
||||
# Add enterprise if it exists
|
||||
if [ -f "${SCHEMA_DIR}/enterprise.schema" ]; then
|
||||
echo "include ${SCHEMA_DIR}/enterprise.schema" >> "$TEMP_DIR/slapd.conf"
|
||||
fi
|
||||
|
||||
log_info "Schema config file:"
|
||||
cat "$TEMP_DIR/slapd.conf"
|
||||
|
||||
# Convert schemas to cn=config format using slaptest
|
||||
log_info "Running slaptest to convert schemas..."
|
||||
if /usr/sbin/slaptest -f "$TEMP_DIR/slapd.conf" -F "$TEMP_DIR/slapd.d" 2>&1; then
|
||||
log_info "Schema conversion successful"
|
||||
else
|
||||
log_error "Schema conversion failed"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy the converted schema files to our slapd.d
|
||||
log_info "Installing converted schemas..."
|
||||
if [ -d "$TEMP_DIR/slapd.d/cn=config/cn=schema" ]; then
|
||||
mkdir -p /etc/openldap/slapd.d/cn=config/cn=schema
|
||||
cp -a "$TEMP_DIR/slapd.d/cn=config/cn=schema/"* /etc/openldap/slapd.d/cn=config/cn=schema/
|
||||
log_info "Schemas installed:"
|
||||
ls -la /etc/openldap/slapd.d/cn=config/cn=schema/
|
||||
else
|
||||
log_error "No schema directory found after conversion"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Fix ownership
|
||||
chown -R ldap:ldap /etc/openldap/slapd.d
|
||||
|
||||
# Now add indexes for schema-defined attributes
|
||||
log_info "Adding database indexes..."
|
||||
|
||||
# Socket URL for ldapi - must use URL-encoded path
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
|
||||
# Start slapd temporarily to add indexes
|
||||
/usr/sbin/slapd -h "$LDAPI_SOCKET" -F /etc/openldap/slapd.d -u ldap -g ldap
|
||||
sleep 2
|
||||
wait_for_slapd 30 "$LDAPI_SOCKET"
|
||||
|
||||
cat > /tmp/add-indexes.ldif << EOF
|
||||
dn: olcDatabase={1}mdb,cn=config
|
||||
changetype: modify
|
||||
add: olcDbIndex
|
||||
olcDbIndex: uid eq,pres,sub
|
||||
-
|
||||
add: olcDbIndex
|
||||
olcDbIndex: uidNumber eq
|
||||
-
|
||||
add: olcDbIndex
|
||||
olcDbIndex: gidNumber eq
|
||||
-
|
||||
add: olcDbIndex
|
||||
olcDbIndex: mail eq,pres,sub
|
||||
-
|
||||
add: olcDbIndex
|
||||
olcDbIndex: memberOf eq
|
||||
-
|
||||
add: olcDbIndex
|
||||
olcDbIndex: member eq
|
||||
EOF
|
||||
|
||||
ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/add-indexes.ldif || log_warn "Some indexes may already exist"
|
||||
|
||||
# Stop temporary slapd
|
||||
pkill slapd || true
|
||||
sleep 2
|
||||
rm -f /tmp/add-indexes.ldif
|
||||
|
||||
log_info "Schema loading complete"
|
||||
108
scripts/init-services.sh
Normal file
108
scripts/init-services.sh
Normal file
@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /scripts/utils.sh
|
||||
|
||||
log_info "Creating service accounts..."
|
||||
|
||||
# Socket URL for ldapi - must use URL-encoded path
|
||||
LDAPI_SOCKET="ldapi://%2Frun%2Fopenldap%2Fldapi"
|
||||
|
||||
# Start slapd temporarily
|
||||
log_info "Starting slapd temporarily for service account creation..."
|
||||
/usr/sbin/slapd -h "$LDAPI_SOCKET" -F /etc/openldap/slapd.d -u ldap -g ldap
|
||||
sleep 2
|
||||
|
||||
# Wait for slapd
|
||||
wait_for_slapd 30 "$LDAPI_SOCKET"
|
||||
|
||||
# Generate passwords for each service if not provided
|
||||
LDAP_SERVICE_KEYCLOAK_PASSWORD="${LDAP_SERVICE_KEYCLOAK_PASSWORD:-$(generate_password)}"
|
||||
LDAP_SERVICE_NEXTCLOUD_PASSWORD="${LDAP_SERVICE_NEXTCLOUD_PASSWORD:-$(generate_password)}"
|
||||
LDAP_SERVICE_GITEA_PASSWORD="${LDAP_SERVICE_GITEA_PASSWORD:-$(generate_password)}"
|
||||
LDAP_SERVICE_POSTFIX_PASSWORD="${LDAP_SERVICE_POSTFIX_PASSWORD:-$(generate_password)}"
|
||||
LDAP_SERVICE_DOVECOT_PASSWORD="${LDAP_SERVICE_DOVECOT_PASSWORD:-$(generate_password)}"
|
||||
LDAP_SERVICE_SSSD_PASSWORD="${LDAP_SERVICE_SSSD_PASSWORD:-$(generate_password)}"
|
||||
|
||||
# Create service accounts LDIF
|
||||
cat > /tmp/service-accounts.ldif << EOF
|
||||
# Keycloak service account
|
||||
dn: cn=keycloak,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: keycloak
|
||||
description: Keycloak LDAP federation service account
|
||||
userPassword: ${LDAP_SERVICE_KEYCLOAK_PASSWORD}
|
||||
|
||||
# Nextcloud service account
|
||||
dn: cn=nextcloud,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: nextcloud
|
||||
description: Nextcloud LDAP backend service account
|
||||
userPassword: ${LDAP_SERVICE_NEXTCLOUD_PASSWORD}
|
||||
|
||||
# Gitea service account
|
||||
dn: cn=gitea,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: gitea
|
||||
description: Gitea LDAP authentication service account
|
||||
userPassword: ${LDAP_SERVICE_GITEA_PASSWORD}
|
||||
|
||||
# Postfix service account
|
||||
dn: cn=postfix,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: postfix
|
||||
description: Postfix virtual mailbox lookup service account
|
||||
userPassword: ${LDAP_SERVICE_POSTFIX_PASSWORD}
|
||||
|
||||
# Dovecot service account
|
||||
dn: cn=dovecot,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: dovecot
|
||||
description: Dovecot authentication service account
|
||||
userPassword: ${LDAP_SERVICE_DOVECOT_PASSWORD}
|
||||
|
||||
# SSSD service account
|
||||
dn: cn=sssd,ou=Services,${LDAP_BASE_DN}
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
cn: sssd
|
||||
description: SSSD NSS/PAM service account
|
||||
userPassword: ${LDAP_SERVICE_SSSD_PASSWORD}
|
||||
EOF
|
||||
|
||||
# Add service accounts
|
||||
ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,${LDAP_BASE_DN}" -w "${LDAP_ADMIN_PASSWORD}" -f /tmp/service-accounts.ldif || \
|
||||
log_warn "Some service accounts may already exist"
|
||||
|
||||
# Output generated passwords to a file for reference
|
||||
cat > /var/lib/openldap/service-passwords.txt << EOF
|
||||
# Service Account Passwords (generated on first run)
|
||||
# IMPORTANT: Store these securely and delete this file after noting passwords
|
||||
|
||||
LDAP_SERVICE_KEYCLOAK_PASSWORD=${LDAP_SERVICE_KEYCLOAK_PASSWORD}
|
||||
LDAP_SERVICE_NEXTCLOUD_PASSWORD=${LDAP_SERVICE_NEXTCLOUD_PASSWORD}
|
||||
LDAP_SERVICE_GITEA_PASSWORD=${LDAP_SERVICE_GITEA_PASSWORD}
|
||||
LDAP_SERVICE_POSTFIX_PASSWORD=${LDAP_SERVICE_POSTFIX_PASSWORD}
|
||||
LDAP_SERVICE_DOVECOT_PASSWORD=${LDAP_SERVICE_DOVECOT_PASSWORD}
|
||||
LDAP_SERVICE_SSSD_PASSWORD=${LDAP_SERVICE_SSSD_PASSWORD}
|
||||
EOF
|
||||
chmod 600 /var/lib/openldap/service-passwords.txt
|
||||
chown ldap:ldap /var/lib/openldap/service-passwords.txt
|
||||
|
||||
log_info "Service account passwords saved to /var/lib/openldap/service-passwords.txt"
|
||||
log_warn "IMPORTANT: Retrieve these passwords and delete the file for security"
|
||||
|
||||
# Stop temporary slapd
|
||||
log_info "Stopping temporary slapd..."
|
||||
pkill slapd || true
|
||||
sleep 2
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/service-accounts.ldif
|
||||
|
||||
log_info "Service account creation complete"
|
||||
91
scripts/utils.sh
Normal file
91
scripts/utils.sh
Normal file
@ -0,0 +1,91 @@
|
||||
#!/bin/sh
|
||||
# Utility functions for OpenLDAP initialization
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
|
||||
}
|
||||
|
||||
# Generate a random password
|
||||
generate_password() {
|
||||
head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24
|
||||
}
|
||||
|
||||
# Hash password using SSHA
|
||||
hash_password() {
|
||||
local password="$1"
|
||||
/usr/sbin/slappasswd -s "$password"
|
||||
}
|
||||
|
||||
# Wait for slapd to be ready
|
||||
# Args: max_attempts [socket_url]
|
||||
wait_for_slapd() {
|
||||
local max_attempts="${1:-30}"
|
||||
local socket_url="${2:-ldapi://%2Frun%2Fopenldap%2Fldapi}"
|
||||
local attempt=0
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if ldapsearch -x -H "$socket_url" -b "" -s base "objectClass=*" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
log_error "slapd did not become ready in time"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Template substitution - replaces ${VAR} with environment variable values
|
||||
process_template() {
|
||||
local template="$1"
|
||||
local output="$2"
|
||||
|
||||
# Use envsubst-like behavior with sed
|
||||
cp "$template" "$output"
|
||||
|
||||
# Replace known variables
|
||||
sed -i "s|\${LDAP_BASE_DN}|${LDAP_BASE_DN}|g" "$output"
|
||||
sed -i "s|\${LDAP_DC}|${LDAP_DC}|g" "$output"
|
||||
sed -i "s|\${LDAP_DOMAIN}|${LDAP_DOMAIN}|g" "$output"
|
||||
sed -i "s|\${LDAP_ORGANISATION}|${LDAP_ORGANISATION}|g" "$output"
|
||||
sed -i "s|\${LDAP_ADMIN_PASSWORD_HASH}|${LDAP_ADMIN_PASSWORD_HASH}|g" "$output"
|
||||
sed -i "s|\${LDAP_CONFIG_PASSWORD_HASH}|${LDAP_CONFIG_PASSWORD_HASH}|g" "$output"
|
||||
sed -i "s|\${LDAP_TLS_CERT_FILE}|${LDAP_TLS_CERT_FILE}|g" "$output"
|
||||
sed -i "s|\${LDAP_TLS_KEY_FILE}|${LDAP_TLS_KEY_FILE}|g" "$output"
|
||||
sed -i "s|\${LDAP_TLS_CA_FILE}|${LDAP_TLS_CA_FILE}|g" "$output"
|
||||
sed -i "s|\${LDAP_TLS_VERIFY_CLIENT}|${LDAP_TLS_VERIFY_CLIENT}|g" "$output"
|
||||
}
|
||||
|
||||
# Default LDAPI socket URL
|
||||
LDAPI_SOCKET="${LDAPI_SOCKET:-ldapi://%2Frun%2Fopenldap%2Fldapi}"
|
||||
|
||||
# Check if a DN exists
|
||||
dn_exists() {
|
||||
local dn="$1"
|
||||
ldapsearch -x -H "$LDAPI_SOCKET" -b "$dn" -s base "objectClass=*" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Add LDIF if it doesn't cause errors (ignore "already exists" errors)
|
||||
ldif_add_safe() {
|
||||
local ldif_file="$1"
|
||||
local result
|
||||
|
||||
result=$(ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,$LDAP_BASE_DN" -w "$LDAP_ADMIN_PASSWORD" -f "$ldif_file" 2>&1) || {
|
||||
if echo "$result" | grep -q "Already exists"; then
|
||||
log_warn "Entry already exists, skipping"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to add LDIF: $result"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user